- PVSM.RU - https://www.pvsm.ru -
Недавно столкнулся с явлением, которое, хотя и хорошо объясняется, но, по крайней мере для меня, не тривиально. Сразу приношу извинения тем, для кого приведенные далее вещи очевидны. Возьмем код:
#include <iostream>
class Base: {
public:
virtual ~Base();
protected:
virtual void helloFromClass() const;
};
class Derived: public Base {
protected:
void helloFromClass() const;
};
Base::~Base() {
helloFromClass();
}
void Base::helloFromClass() const {
std::cout << "Hello from Basen";
}
void Derived::helloFromClass const {
std::cout << "Hello from Derivedn";
}
Derived d;
int main() {
return 0;
}
Я ожидал, что из-за динамического полиморфизма класса Base программа напишет «Hello from Derived» во время вызова деструктора Base. Сразу хочу сказать, что виртуальность деструктора в данном примере не играет никакого значения, речь далее пойдет о виртуальности helloFromClass(). Те, кто также как и я поначалу, не верят глазам своим, могут скомпилировать и запустить программу: видим: «Hello from Base».
Вспоминаем, что согласно правилам C++, при уничтожении объекта последовательно вызываются деструкторы ~Derived(), потом ~Base(), первый создан компиляторм по умолчанию и не производит никакого вывода. Почему же ~Base() вызывает Base::helloFromClass(), а не Derived::helloFromClass()?
Во-первых, объект d «состоит из двух частей»: Base и Derived. На момент вызова ~Base() «половинка Derived» объекта d уже уничтожена, а сам d частично уничтожен. Поэтому вызов Derived::helloFromClass() вообще не корректен, поскольку произошел бы для несуществующего объекта — «половинки Derived» объекта d. Например, если бы Derived::helloFromClass() обращался к данным Derived, то это было бы вообще обращение к деинициализированным данным, например, к памяти занятой уже другим объектом или не отображенной в пространство процесса. То есть, undefined behavior во всей красе.
Во-вторых, очевидно, вызов Base::helloFromClass() произошел потому, что указатель на таблицу виртуальных методов d был изменен. То есть, порядок действий таков: вызов d.~Derived(), замена таблицы виртуальных методов d, вызов d.~Base(). Лень проверять, поэтому не знаю, является такое поведение стандартным или реализовано конкретным компилятором (в моем случае gcc4).
Независимо от того, является приведенный код undefined behavior (то есть в стандарте замена таблицы не требуется) или «тёмным углом стандарта» (если этот стандарт требует замены таблицы) выводы напрашиваются одни и те же:
class Base2 {
public:
virtual ~Base2();
void helloFromClass() const {
helloFromClassCall();
}
protected:
virtual void helloFromClassCall() const;
};
Base2::~Base2() {
helloFromClass(); //опосредованный вызов виртуального helloFromClassCall()
}
class Base3 {
public:
virtual ~Base3();
protected:
virtual void helloFromClass() const = 0;
};
Base3::~Base3() {
helloFromClass(); //вызов чистой виртуальной функции: не в деструкторе не произошел бы
}
Автор: oleg1977
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/24713
Ссылки в тексте:
[1] Источник: http://habrahabr.ru/post/165685/
Нажмите здесь для печати.