我的一位同事问,为什么他们的测试会崩溃。异常描述是“纯虚拟调用”。快速查看代码(参见下面的过于简化的示例)表明,他们的测试引用了作用域过期后在堆栈上创建的对象,因为他们保留了过时的裸指针。简单的。
然后我的同事问,为什么他们的测试运行得更早,因为她唯一添加的(我可能会说正确)是抽象类中的虚拟析构函数!坦率地说,我不知道,我希望有人能启发我。
我调试了应用程序,在从 IFooListener
中删除 desctructor 声明后,应用程序运行正常,vftable 指针指向具体类的 vftable。因此程序运行并且 printfs 打印。一旦虚析构函数进来,对象退出作用域后,vftable指针指向抽象类的vftable。
我还尝试使用非虚拟析构函数,结果是一样的——应用程序崩溃并正常运行。
我该如何理解? 如果没有在抽象类中声明和定义析构函数,无论是否为虚函数,都不会生成和调用基类析构函数来清理 vftable?
(在Windows下用VS2008编译器测试调试)
下面是麻烦代码的简化重构:
#include <stdio.h>
#include <vector>
class IFooListener
{
public:
virtual void onFoo() = 0;
virtual ~IFooListener();
};
IFooListener::~IFooListener() {
// nothing to do!
}
class TestListener : public IFooListener
{
public:
TestListener()
{
m_FooCounter = 0;
}
virtual void onFoo()
{
++m_FooCounter;
printf("Got foo!\n");
}
unsigned int getFooCount()
{
return m_FooCounter;
}
private:
unsigned int m_FooCounter;
};
class FooSource
{
public:
void sendFoo();
void addListner(IFooListener * pListener);
private:
std::vector<IFooListener *> m_Listeners;
};
void FooSource::sendFoo()
{
for (std::vector<IFooListener *>::const_iterator i = m_Listeners.begin(); i != m_Listeners.end(); i++)
{
IFooListener * listener = *i;
listener->onFoo();
}
}
void FooSource::addListner(IFooListener * a_pListener)
{
m_Listeners.push_back(a_pListener);
}
void runTest(FooSource& source, int id)
{
switch (id) {
case 1:
{
TestListener listener1;
source.addListner(&listener1);
source.sendFoo();
if (0 == listener1.getFooCount())
{
printf("Test 1 failed!\n");
}
break;
}
case 2:
{
TestListener listener1;
TestListener listener2;
source.addListner(&listener1);
source.addListner(&listener2);
source.sendFoo();
if ((0 == listener1.getFooCount()) || (0 == listener2.getFooCount()))
{
printf("Test 2 failed!\n");
}
break;
}
default:
printf("Unknown test\n");
break;
}
}
int main()
{
FooSource source;
runTest(source, 1);
runTest(source, 2);
printf("Testing finished\n");
return 0;
}
最佳答案
仅解决析构函数问题:
这里没有发生多态破坏,因此父级中缺少虚拟析构函数不会阻止子级被清理。具体来说,本地 TestListener
有自己的析构函数,当它超出范围时调用,因为编译器已经知道被销毁对象的确切类型!由于它知道确切的类型,因此不需要使用虚拟析构函数来清理子实例。
它在一种情况下崩溃而没有崩溃的原因(注意:我没有说它起作用是因为它不起作用 - 结果只是看起来起作用)是因为两者这些选项中的一些是未定义行为的合法示例。实际上,这是因为处理抽象析构函数调用的代码与处理非抽象调用的代码不同。
关于c++ - 从 C++ 中的抽象类派生的类中没有销毁阶段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29126939/