17.5.8. Виртуальные функции, конструкторы и деструкторы
17.5.8. Виртуальные функции, конструкторы и деструкторы
Как мы видели в разделе 17.4, для объекта производного класса сначала вызывается конструктор базового, а затем производного класса. Например, при таком определении объекта NameQuery
NameQuery poet( "Orlen" );
сначала будет вызван конструктор Query, а потом NameQuery.
При выполнении конструктора базового класса Query часть объекта, соответствующая классу NameQuery, остается неинициализированной. По существу, poet - это еще не объект NameQuery, сконструирован лишь его подобъект.
Что должно происходить, если внутри конструктора базового класса вызывается виртуальная функция, реализации которой существуют как в базовом, так и в производном классах? Какая из них должна быть вызвана? Результат вызова реализации из производного класса в случае, когда необходим доступ к его членам, оказался бы неопределенным. Вероятно, выполнение программы закончилось бы крахом.
Чтобы этого не случилось, в конструкторе базового класса всегда вызывается реализация виртуальной функции, определенная именно в базовом. Иными словами, внутри такого конструктора объект производного класса рассматривается как имеющий тип базового.
То же самое справедливо и внутри деструктора базового класса, вызываемого для объекта производного. И в этом случае часть объекта, относящаяся к производному классу, не определена: не потому, что еще не сконструирована, а потому, что уже уничтожена.
Упражнение 17.12
Внутри объекта NameQuery естественное внутреннее представление вектора позиций - это указатель, который инициализируется указателем, хранящимся в отображении слов. Оно же является и наиболее эффективным, так как нам нужно скопировать лишь один адрес, а не каждую пару координат. Классы AndQuery, OrQuery и NotQuery должны конструировать собственные векторы позиций на основе вычисления своих операндов. Когда время жизни объекта любого из этих классов завершается, ассоциированный с ним вектор позиций необходимо удалить. Когда же заканчивается время жизни объекта NameQuery, вектор позиций удалять не следует. Как сделать так, чтобы вектор позиций был представлен указателем в базовом классе Query и при этом его экземпляры для объектов AndQuery, OrQuery и NotQuery удалялись, а для объектов NameQuery - нет? (Заметим, что нам не разрешается добавить в класс Query признак, показывающий, нужно ли применять оператор delete к вектору позиций!)
Упражнение 17.13
Что неправильно в приведенном определении класса:
class AbstractObject {
public:
~AbstractObject();
virtual void doit() = 0;
// ...
};
Упражнение 17.14
Даны такие определения:
NameQuery nq( "Sneezy" );
Query q( nq );
Query *pq =
Почему в инструкции
pq-eval();
вызывается экземпляр eval() из класса NameQuery, а в инструкции
q.eval();
экземпляр из Query?
Упражнение 17.15
Какие из повторных объявлений виртуальных функций в классе Derived неправильны:
(a) Base* Base::copy( Base* );
Base* Derived::copy( Derived* );
(b) Base* Base::copy( Base* );
Derived* Derived::copy( Vase* );
(c) ostream& Base::print( int, ostream&=cout );
ostream& Derived::print( int, ostream& );
(d) void Base::eval() const;
void Derived::eval();
Упражнение 17.16
Маловероятно, что наша программа заработает при первом же запуске и в первый раз, когда прогоняется с реальными данными. Средства отладки полезно включать уже на этапе проектирования классов. Реализуйте в нашей иерархии классов Query виртуальную функцию debug(), которая будет отображать члены соответствующих классов. Поддержите управление уровнем детализации двумя способами: с помощью аргумента, передаваемого функции debug(), и с помощью члена класса. (Последнее позволяет включать или отключать выдачу отладочной информации в отдельных объектах.)
Упражнение 17.17
Найдите ошибку в следующей иерархии классов:
class Object {
public:
virtual void doit() = 0;
// ...
protected:
virtual ~Object();
};
class MyObject : public Object {
public:
MyObject( string isA );
string isA() const;
protected:
string _isA;
};