一个很常见的情况:试图从构造函数中调用一个纯虚方法...
构造函数
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 类型,并且该方法是virtual pure,因此_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 析构函数被调用之前发生了什么?我没有;)
结论
永远不要在构造函数或析构函数中调用虚方法。
如果不是这样,你的内存就会损坏,运气不好;)