Что должно происходить, если внутри конструктора базового класса вызывается виртуальная функция, реализации которой существуют как в базовом, так и в производном классах? Какая из них должна быть вызвана? Результат вызова реализации из производного класса в случае, когда необходим доступ к его членам, оказался бы неопределенным. Вероятно, выполнение программы закончилось бы крахом.
Чтобы этого не случилось, в конструкторе базового класса всегда вызывается реализация виртуальной функции, определенная именно в базовом. Иными словами, внутри такого конструктора объект производного класса рассматривается как имеющий тип базового.
То же самое справедливо и внутри деструктора базового класса, вызываемого для объекта производного. И в этом случае часть объекта, относящаяся к производному классу, не определена: не потому, что еще не сконструирована, а потому, что уже уничтожена.
Упражнение 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;
};
17.6. Почленная инициализация и присваивание A
При проектировании класса мы должны позаботиться о том, чтобы почленная инициализация (см. раздел 14.6) и почленное присваивание (см. раздел 14.7) были реализованы правильно и эффективно. Рассмотрим связь этих операций с наследованием.
До сих пор мы не занимались явной обработкой почленной инициализации. Посмотрим, что происходит в нашей иерархии классов Query по умолчанию.
В абстрактном базовом классе Query определены три нестатических члена:
class Query {
public: // ...
protected:
int _paren;
setshort *_solition;
vectorlocation _loc;
// ...
};
Член _solution, если он установлен, адресует множество, память для которого выделена в хипе функцией-членом _vec2set(). Деструктор Query применяет к _solution оператор delete.