Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

892

17.5.4. Виртуальные функции и аргументы по умолчанию

#include <iostream>

class base { public:

virtual int foo( int ival = 1024 ) {

cout << "base::foo() -- ival: " << ival << endl; return ival;

}

// ...

};

class derived : public base { public:

virtual int foo( int ival = 2048 ) {

cout << "derived::foo() -- ival: " << ival << endl; return ival;

}

// ...

Рассмотрим следующую простую иерархию классов:

};

Проектировщик класса хотел, чтобы при вызове без параметров реализации foo() из

base b;

base *pb = &b;

//вызывается base::foo( int )

//предполагалось, что будет возвращено 1024

базового класса по умолчанию передавался аргумент 1024: pb->foo();

Кроме того, разработчик хотел, чтобы при вызове его реализации foo() без параметров

derived d; base *pb = &d;

//вызывается derived::foo( int )

//предполагалось, что будет возвращено 2048

использовался аргумент по умолчанию 2048: pb->foo();

Однако в C++ принята другая семантика механизма виртуализации. Вот небольшая программа для тестирования нашей иерархии классов:

С++ для начинающих

893

int main()

{

derived *pd = new derived; base *pb = pd;

int val = pb->foo();

cout << "main() : val через base: " << val << endl;

val = pd->foo();

cout << "main() : val через derived: " << val << endl;

}

После компиляции и запуска программа выводит следующую информацию:

derived::foo() -- ival: 1024 main() : val через base: 1024 derived::foo() -- ival: 2048 main() : val через derived: 2048

При обоих обращениях реализация foo() из производного класса вызывается корректно,

поскольку фактически вызываемый экземпляр определяется во время выполнения на основе типа класса, адресуемого pd и pb. Но передаваемый foo() аргумент по умолчанию определяется не во время выполнения, а во время компиляции на основе типа объекта, через который вызывается функция. При вызове foo() через pb аргумент по умолчанию извлекается из объявления base::foo() и равен 1024. Если же foo() вызывается через pd, то аргумент по умолчанию извлекается из объявления derived::foo() и равен 2048.

Если реализации из производного класса при вызове через указатель или ссылку на базовый класс по умолчанию передается аргумент, указанный в базовом классе, то зачем задавать аргумент по умолчанию для реализации из производного класса?

Нам могут понадобиться различные аргументы по умолчанию в зависимости не от реализации foo() в конкретном производном классе, а от типа указателя или ссылки, через которые функция вызвана. Например, значения 1024 и 2048 – это размеры изображений. Когда нужно получить менее детальное изображение, вызываем foo() через класс base, а когда более детальное через derived.

Но если мы все-таки хотим, чтобы аргумент по умолчанию, передаваемый foo(), зависел от фактически вызванного экземпляра? К сожалению, механизм виртуализации такую возможность не поддерживает. Однако разрешается задать такой аргумент по умолчанию, который для вызванной функции означает, что пользователь не передал никакого значения. Тогда реальное значение, которое функция хотела бы видеть в качестве аргумента по умолчанию, объявляется локальной переменной и используется, если ничего другого не передано:

С++ для начинающих

894

void base::

foo( int ival = base_default_value )

{

int real_default_value = 1024;

// настоящее значение по умолчанию

if ( ival == base_default_value ) ival = real_default_value;

// ...

}

Здесь base_default_value значение, согласованное между всеми классами иерархии, которое явно говорит о том, что пользователь не передал никакого аргумента.

void derived::

foo( int ival = base_default_value )

{

int real_default_value = 2048;

if ( ival == base_default_value ) ival = real_default_value;

// ...

Производный класс может быть реализован аналогично:

}

17.5.5. Виртуальные деструкторы

void doit_and_bedone( vector< Query* > *pvec )

{

// ...

for ( ; it != end_it; ++it )

{

Query *pq = *it; // ...

delete pq;

}

Вданной функции мы применяем оператор delete:

}

Чтобы функция выполнялась правильно, применение delete должно вызывать деструктор того класса, на который указывает pq. Следовательно, необходимо объявить деструктор Query виртуальным:

С++ для начинающих

895

class Query { public:

virtual ~Query() { delete _solution; } // ...

};

Деструкторы всех производных от Query классов автоматически считаются виртуальными. doit_and_bedone() выполняется правильно.

Поведение деструктора при наследовании таково: сначала вызывается деструктор производного класса, в случае pq виртуальная функция. По завершении вызывается деструктор непосредственного базового класса статически. Если деструктор объявлен встроенным, то в точке вызова производится подстановка. Например, если pq указывает на объект класса AndQuery, то

delete pq;

приводит к вызову деструктора класса AndQuery за счет механизма виртуализации. После этого статически вызывается деструктор BinaryObject, а затем снова статически деструктор Query.

class Query { public: // ...

protected:

virtual ~Query(); // ...

};

class NotQuery : public Query { public:

~NotQuery();

//...

Вследующей иерархии классов

};

уровень доступа к конструктору NotQuery открытый при вызове через объект NotQuery, но защищенный при вызове через указатель или ссылку на объект Query. Таким образом, виртуальная функция подразумевает уровень доступа того класса, через объект

int main()

{

Query *pq = new NotQuery;

// ошибка: деструктор является защищенным delete pq;

которого вызывается:

}