Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 42



>virtual ~AWOV() = 0; // объявление чисто виртуального

>}; // деструктора


Этот класс включает в себя чисто виртуальную функцию, поэтому он абстрактный. А раз в нем объявлен виртуальный деструктор, то можно не беспокоиться о том, что деструкторы базовых классов не будут вызваны. Однако есть одна тонкость: вы должны предоставить определение чисто виртуального деструктора:


>AWOV::~AWOV(){}; // определение чисто виртуального деструктора


Дело в том, что сначала всегда вызывается деструктор «самого производного» класса (то есть находящегося на нижней ступени иерархии наследования), а затем деструкторы каждого базового класса. Компилятор сгенерирует вызов ~AWOV из деструкторов производных от него классов, а значит, вы должны позаботиться о его реализации. Если этого не сделать, компоновщик будет недоволен.

Правило включения в базовые классы виртуальных деструкторов касается только полиморфных базовых классов, то есть таких, которые позволяют манипулировать объектами производных классов с помощью указателя на базовый. TimeKeeper – полиморфный базовый класс, мы ожидаем, что при наличии указателя на объект TimeKeeper сможем манипулировать объектами AtomicClock и WaterClock.

Не все базовые классы разрабатываются с учетом полиморфизма. Например, и стандартный тип string, и типы STL-контейнеров спроектированы так, что не допускают возможности использования в качестве базовых, так как не являются полиморфными. Некоторые классы предназначены служить в качестве базовых, но полиморфно использоваться не могут; примером могут служить класс Uncopyable из правила 6 и класс input_iterator_tag из стандартной библиотеки (см. правило 47). Таким классам не нужны виртуальные деструкторы.

Что следует помнить

• Полиморфные базовые классы должны объявлять виртуальные деструкторы. Если класс имеет хотя бы одну виртуальную функцию, он должен иметь виртуальный деструктор.

• В классах, не предназначенных для использования в качестве базовых или для полиморфного применения, не следует объявлять виртуальные деструкторы.

Правило 8: Не позволяйте исключениям покидать деструкторы

C++ не запрещает использовать исключения в деструкторах, но это, безусловно, очень нежелательная практика. На то есть серьезная причина. Рассмотрим пример:


>class Widget {

>public:

>...

>~Widget() {...} // предположим, здесь есть исключение

>};

>void doSomething()

>{

>std::vector v;

>...

>} // здесь v автоматически уничтожается


Когда вектор v уничтожается, он отвечает за уничтожение всех объектов Widget, которые в нем содержатся. Предположим, что v содержит 10 объектов Widget, и во время уничтожения первого из них возбужается исключение. Остальные девять объектов Widget также должны быть уничтожены (иначе ресурсы, выделенные для них, будут потеряны), поэтому необходимо вызвать и их деструкторы. Но представим, что в это время деструктор второго объекта Widget также возбудит исключение. Тогда возникнет сразу два одновременно активных исключения, а это слишком много для C++. В зависимости от конкретных условий исполнение программы либо будет прервано, либо ее поведение окажется неопределенным. В этом примере как раз имеет место второй случай. И так будет происходить при использовании любого библиотечного контейнера (например, list, set), любого контейнера TR1 (см. правило 54) и даже массива. И причина этой проблемы не в контейнерах или массивах. Преждевременное завершение программы или неопределенное поведение здесь является результатом того, что деструкторы возбуждают исключения. C++