Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
199
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

ошибка проявляется, так что для нас она останется лишь потенциальной угрозой;

∙ с другой стороны, если ошибка не найдена вплоть до момента использования, то код может оказаться замусоренным ошибками, проявляющимися в самый неподходящий момент к удивлению программиста. При такой стратегии успешная компиляция говорит не об отсутствии семантических ошибок, а лишь о том, что программа не исполняет код, нарушающий семантические правила языка.

Выдача сообщения об ошибке в точке использования – это одна из форм отложенного вычисления, распространенного метода повышения производительности программ. Он часто применяется для того, чтобы отложить потенциально дорогую операцию выделения или инициализации ресурса до момента, когда в нем возникнет реальная необходимость. Если ресурс так и не понадобится, мы сэкономим на ненужных подготовительных операциях. Если же он потребуется, но не сразу, мы растянем инициализацию программы на более длительный период.

В C++ потенциальные ошибки “комбинирования”, связанные с перегруженными функциями, шаблонами и наследованием классов, обнаруживаются в точке использования, а не в точке объявления. (Мы полагаем, что это правильно, поскольку необходимость выявлять все возможные ошибки, которые можно допустить в результате комбинирования многочисленных компонентов, – пустая трата времени). Следовательно, для обнаружения и устранения латентных ошибок необходимо тщательно тестировать код. Подобные ошибки, возникающие при комбинировании двух или более больших компонентов, допустимы; однако в пределах одного компонента, такого, как иерархия классов Query, их быть не должно.

17.4.5. Деструкторы

Когда заканчивается время жизни объекта производного класса, автоматически вызываются деструкторы производного и базового классов (если они определены), а также деструкторы всех объектов-членов. Например, если имеется объект класса

NameQuery:

NameQuery nq( "hyperion" );

то порядок вызова деструкторов следующий: сначала деструктор NameQuery, затем деструктор string для члена _name и наконец деструктор базового класса. В общем случае эта последовательность противоположна порядку вызова конструкторов.

Вот деструкторы нашего базового Query и производных от него (все они объявлены открытыми членами соответствующих классов):

inline Query::

~Query(){ delete _solution; }

inline NotQuery:: ~NotQuery(){ delete _op; }

inline OrQuery::

~OrQuery(){ delete _lop; delete _rop; }

inline AndQuery::

~AndQuery(){ delete _lop; delete _rop; }

Отметим два аспекта:

∙ мы не предоставляем явного деструктора NameQuery, потому что никаких специальных действий по очистке его объекта предпринимать не нужно. Деструкторы базового класса и класса string для члена _name вызываются автоматически;

в деструкторах производных классов оператор delete применяется к указателю типа Query*. Чтобы вызвать не деструктор Query, а деструктор класса того объекта, который фактически адресуется этим указателем, мы должны объявить деструктор базового Query виртуальным. (Более подробно о виртуальных функциях вообще и о виртуальных деструкторах в частности мы поговорим в следующем разделе.)

Внашей реализации неявно подразумевалось, что память для операндов, указатели на которые имеются в объектах классов NotQuery, OrQuery и AndQuery, выделена из хипа.

Именно поэтому в деструкторах мы применяли к этим указателям оператор delete. Но язык не позволяет обеспечить истинность такого предположения, так как в нем нет различий между адресами в хипе и вне его. С этой точки зрения наша реализация не застрахована от ошибок.

В разделе 17.7 мы инкапсулируем выделение памяти и конструирование объектов иерархии Query в управляющий класс UserQuery. Это гарантирует выполнение нашего предположения. На уровне программы в целом следует перегрузить операторы new и delete для классов иерархии. Например, можно поступить следующим образом. Оператор new устанавливает в объекте флажок, говорящий, что память для него выделена из хипа. Перегруженный оператор delete проверяет этот флажок: если он есть, то память освобождается с помощью стандартного оператора delete.

Упражнение 17.7

Идентифицируйте конструкторы и деструкторы базового и производных классов для той иерархии, которую вы выбрали в упражнении 17.2 (раздел 17.1).

Упражнение 17.8

Измените реализацию класса OrQuery так, чтобы он был производным от BinaryQuery. Упражнение 17.9 Найдите ошибку в следующем определении класса:

class Object { public:

virtual ~Object(); virtual string isA();

protected: string _isA;

private:

Object( string s ) : _isA( s ) {}

};

Упражнение 17.10

class ConcreteBase { public:

explicit ConcreteBase( int ); virtual ostream&

print( ostream& ); virtual ~Base();

static int object_count(); protected:

int _id;

static int _object_count;

Дано определение базового класса:

};

(a) class C1 : public ConcreteBase

{

public:

C1( int val )

: _id( _object_count++ ) {}

// ...

Что неправильно в следующих фрагментах:

};

(b)class C2 : public C1 { public:

C2( int val )

: ConcreteBase( val ), C1( val ) {}

// ...

};