您遇到的任何未定义的行为都可能导致这种情况.例如:
另请参阅问题实际可能的未定义行为的最坏例子是什么?和什么是所有常见的未定义行为,一个C++程序员应该知道的?.
最好的办法是使用边界和内存检查器,以帮助进行大量调试.
一个非常常见的情况:尝试从构造函数中调用纯虚方法...
构造函数
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Run Code Online (Sandbox Code Playgroud)
现在,假设以下实现 Interface()
Interface::Interface() {}
Run Code Online (Sandbox Code Playgroud)
一切都很好:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
Run Code Online (Sandbox Code Playgroud)
在构造函数之后调用pure是如此痛苦,最好将代码分解为正确吗?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
Run Code Online (Sandbox Code Playgroud)
然后我们可以在一行中完成!
Concrete myConcrete; // CRASHES VIOLENTLY
Run Code Online (Sandbox Code Playgroud)
为什么?
因为对象是自下而上构建的.我们来看看吧.
构建Concrete类的说明(粗略)
分配足够的内存(当然),以及_vtable的足够内存(每个虚函数1个函数指针,通常按照它们被声明的顺序,从最左边的基数开始)
调用Concrete构造函数(你看不到的代码)
a>调用Interface构造函数,用它的指针初始化_vtable
b>调用Interface构造函数的主体(你写的那个)
c>覆盖_vtable中用于这些方法的指针.具体覆盖
d>调用Concrete构造函数的主体(你写的那个)
所以有什么问题 ?好吧,看看b>并c>订购;)
当您virtual从构造函数中调用方法时,它不会执行您希望的操作.它确实转到_vtable来查找指针,但_vtable尚未完全初始化.因此,对于所有重要的事情,影响:
D() { this->call(); }
Run Code Online (Sandbox Code Playgroud)
实际上是:
D() { this->D::call(); }
Run Code Online (Sandbox Code Playgroud)
从构造函数中调用虚方法时,如果没有构建对象的完整动态类型,则调用当前构造函数的静态类型.
在我的Interface/ Concrete示例中,它表示Interface类型,并且该方法是虚拟纯的,因此_vtable不包含实际指针(例如,0x0或0x01,如果您的编译器足够友好以设置调试值来帮助您).
析构函数
巧妙地,让我们检查一下Destructor案例;)
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;
}
Run Code Online (Sandbox Code Playgroud)
那么毁灭会发生什么?那么_vtable工作得很好,并且调用了真正的运行时类型......这里的含义是未定义的行为,因为谁知道m_data在删除它之后和Interface调用析构函数之前发生了什么?我不 ;)
结论
永远不要在构造函数或析构函数中调用虚方法.
如果不是这样,你就会留下内存腐败,运气不好;)