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



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

• Деструкторы никогда не должны возбуждать исключений. Если функция, вызываемая в деструкторе, может это сделать, то деструктор обязан перехватывать все исключения, а затем «проглатывать» их либо прерывать программу.

• Если клиенты класса нуждаются в возможности реагировать на исключения во время некоторой операции, то класс должен предоставить обычную функцию (то есть не деструктор), которая эту операцию выполнит.

Правило 9: Никогда не вызывайте виртуальные функции в конструкторе или деструкторе

Начну с повторения: вы не должны вызывать виртуальные функции во время работы конструкторов или деструкторов, потому что эти вызовы будут делать не то, что вы думаете, и результатами их работы вы будете недовольны. Если вы – программист на Java или C#, то обратите на это правило особое внимание, потому что это в этом отношении C++ ведет себя иначе.

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


>class Transaction { // базовый класс для всех

>public: // транзакций

>Transaction();

>virtual void logTransaction() const = 0; // выполняет зависящую от типа

>// запись в протокол

>...

>};

>Transaction::Transaction() // реализация конструктора

>{ // базового класса

>...

>logTransaction();

>}

>class BuyTransaction: public Transaction { // производный класс

>public:

>virtual void logTransaction() const = 0; // как протоколировать

>// транзакции данного типа

>...

>};

>class SellTransaction: public Transaction { // производный класс

>public:

>virtual void logTransaction() const = 0; // как протоколировать

>// транзакции данного типа

>...

>};


Посмотрим, что произойдет при исполнении следующего кода:


>BuyTransaction b;


Ясно, что будет вызван конструктор BuyTransaction, но сначала должен быть вызван конструктор Transaction, потому что части объекта, принадлежащие базовому классу, конструируются прежде, чем части, принадлежащие производному классу. В последней строке конструктора Transaction вызывается виртуальная функция logTransaction, тут-то и начинаются сюрпризы. Здесь вызывается та версия logTransaction, которая определена в классе Transaction, а не в BuyTransaction, несмотря на то что тип создаваемого объекта – BuyTransaction. Во время конструирования базового класса не вызываются виртуальные функции, определенные в производном классе. Объект ведет себя так, как будто он принадлежит базовому типу. Короче говоря, во время конструирования базового класса виртуальных функций не существует.