class Transaction {
public:
Transaction()
{ init(); } // вызов невиртуальной функции
Virtual void logTransaction() const = 0;
…
private:
void init()
{
…
logTransaction(); // а это вызов виртуальной
// функции!
}
};Концептуально этот код не отличается от приведенного выше, но он более коварный, потому что обычно будет скомпилирован и скомпонован без предупреждений. В этом случае, поскольку logTransaction – чисто виртуальная функция класса Transaction, в момент ее вызова большинство систем времени исполнения прервут программу (обычно выдав соответствующее сообщение). Однако если logTransaction будет «нормальной» виртуальной функцией, у которой в классе Transaction есть реализация, то эта функция и будет вызвана, и программа радостно продолжит работу, оставляя вас в недоумении, почему при создании объекта производного класса была вызвана неверная версия logTransaction. Единственный способ избежать этой проблемы – убедиться, что ни один из конструкторов и деструкторов не вызывает виртуальных функций при создании или уничтожении объекта, и что все функции, к которым они обращаются, следуют тому же правилу.
Но как вы можете убедиться в том, что вызывается правильная версия log-Transaction при создании любого объекта из иерархии Transaction? Понятно, что вызов виртуальной функции объекта из конструкторов не годится.
Есть разные варианты решения этой проблемы. Один из них – сделать функцию logTransaction невиртуальной в классе Transaction, затем потребовать, чтобы конструкторы производного класса передавали необходимую для записи в протокол информацию конструктору Transaction. Эта функция затем могла бы безопасно вызвать невиртуальную logTransaction. Примерно так:class Transaction {
public:
explicit Transaction(const std::string& loginfo);
void logTransaction(const std::string& loginfo) const; // теперь –
// невиртуальная
// функция
…
};
Transaction::Transaction(const std::string& loginfo)
{
…
logTransaction(loginfo); // теперь –
// невиртуальный
// вызов
}
class BuyTransaction : public Transaction {
public:
BuyTransaction(
: Transaction(createLogString(
{…} // для записи в протокол
… // конструктору базового
// класса
private:
static std::string createLogString(
Другими словами, если вы не можете вызывать виртуальные функции из конструктора базового класса, то можете компенсировать это передачей необходимой информации конструктору базового класса из конструктора производного.
В этом примере обратите внимание на применение закрытой статической функции createLogString в BuyTransaction. Использование вспомогательной функции для создания значения, передаваемого конструктору базового класса, часто удобнее (и лучше читается), чем отслеживание длинного списка инициализации членов для передачи базовому классу того, что ему нужно. Сделав эту функцию статической, мы избегаем опасности нечаянно сослаться на неинициализированные данные-члены класса BuyTransaction. Это важно, поскольку тот факт, что эти данные-члены еще не определены, и является основной причиной, почему нельзя вызывать виртуальные функции из конструкторов и деструкторов.