Почему это не может работать?
Этот дизайн не будет работать из-за способа создания объектов.
Когда вы создаете Derived
, первое, что происходит, это то, что объект Base
создается с помощью конструктора Base
. В данный момент нет объекта Derived
, поэтому, если бы вы вызывали виртуальную функцию в конструкторе Base
, она была бы действительной для класса Base
, пока вы не покинете конструктор Base
. тело.
Это разрешено стандартом, но с ограничениями:
[base.class.init] / 16 : функции-члены (, включая виртуальныефункции-члены ) могут вызываться для строящегося объекта. (...) Однако, если эти операции выполняются в инициализаторе ctor (или в функции, вызываемой прямо или косвенно из инициализатора ctor) до завершения всех инициализаторов mem для базовых классов, программа имеет неопределенное поведение.
Эти ограничения не распространяются на виртуальные функции, которые вызываются из тела конструктора, поскольку тело выполняется после всех инициализаторов.
Но в вашем случае виртуальная функция является чисто виртуальной для Base
. Так что это UB в соответствии со следующим предложением:
[class.abstract] / 6 : функции-члены могут вызываться из конструктора (или деструктора) абстрактного класса;эффект виртуального вызова чистой виртуальной функции прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), равен undefined .
Какова альтернатива
К сожалению, нет другой реальной альтернативы, кроме использования двухэтапной инициализации , в которой вы сначала создаете объект, а затем вызываете функцию инициализации перед использованием объекта.
Единственная проблема при таком подходе - это риск забыть вызов функции инициализации. Вы можете защитить себя от этого:
- , используя флаг, чтобы указать, произошла ли инициализация, и проверьте этот флаг во всех функциях-членах (да, это немного накладные расходы).
- если у вас есть абстрактный класс, вы можете использовать фабричный шаблон (фабричный метод или абстрактную фабрику). Вы можете позволить фабрике сделать звонок.
- вы можете использовать шаблон компоновщика и убедиться, что конструктор виден только компоновщику, который также не забудет инициализацию.
Другие проблемы с вашим кодом
Вы должны быть осторожны с тем, как вы "вызываете" базовый конструктор из производного конструктора:
class Derived : Base {
public:
Derived() : Base(42) // this is the correct place !
{
//Base::Base(42); //<==== OUCH !!! NO !! This creates a temporary base object !!
}
...
};
Вы бытакже нужно быть осторожным с чистыми виртуалами (я не знаю, является ли это опечаткой или ваш компилятор может скомпилировать ваш код):
virtual void someMethod() = 0; // if it's abstract, no pending {} !