Очень распространенный случай: попытка вызвать чисто виртуальный метод из конструктора ...
Конструкторы
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Теперь предположим следующую реализацию Interface()
Interface::Interface() {}
Тогда все в порядке:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
Это такая боль - вызывать pure после конструктора, было бы лучше разложить на части код, верно?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
Тогда мы можем сделать это в одну строку !!
Concrete myConcrete; // CRASHES VIOLENTLY
Почему?
Потому что объект построен снизу вверх. Давайте посмотрим на это.
Инструкция по созданию Concrete
класса (примерно)
Выделите достаточно памяти (конечно) и достаточно памяти для _vtable (1 указатель функции на виртуальную функцию, обычно в порядке их объявления, начиная с крайней левой базы)
Вызов Concrete
конструктор (код, который вы не видите)
a> Вызовите Interface
конструктор, который инициализирует _vtable с его указателями
b> Вызовите Interface
тело конструктора (вы это написали)
c> Переопределить указатели в _vtable для этих методов. Переопределение бетона
d> Вызовите Concrete
тело конструктора (вы это написали)
Так в чем проблема? Ну, посмотрите на b>
и c>
порядок;)
Когда вы вызываете метод virtual
из конструктора, он не делает то, на что вы надеялись. Он идет в _vtable для поиска указателя, но _vtable
еще не полностью инициализирован. Итак, для всего, что имеет значение, эффект:
D() { this->call(); }
на самом деле:
D() { this->D::call(); }
При вызове виртуального метода из Конструктора вы не получаете полный динамический тип строящегося объекта, у вас есть статический тип текущего конструктора.
В моем примере Interface
/ Concrete
это означает тип Interface
, а метод является виртуально чистым, поэтому _vtable не содержит реального указателя (например, 0x0 или 0x01, если ваш компилятор достаточно дружественен) чтобы настроить значения отладки, чтобы помочь вам там.
деструкторы
По совпадению, давайте рассмотрим случай Деструктора;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
Так что же происходит при разрушении? Хорошо, _vtable работает хорошо, и вызывается реальный тип времени выполнения ... что это означает здесь, однако, является неопределенным поведением, потому что кто знает, что случилось с m_data
после того, как он был удален и до того, как был вызван деструктор Interface
? Не знаю;)
Заключение
Никогда не вызывайте виртуальные методы из конструкторов или деструкторов.
Если это не так, у вас останется повреждение памяти, неудача;)