C++中抽象类派生类中的不破坏阶段

C++中抽象类派生类中的不破坏阶段,c++,inheritance,abstract-class,virtual-functions,C++,Inheritance,Abstract Class,Virtual Functions,我的一个同事问,为什么他们的测试会失败。异常描述是纯虚拟调用。快速查看下面一个过于简化的示例代码表明,他们的测试引用了在其作用域过期后在堆栈上创建的对象,因为它们保留了过时的裸指针。简单 然后我的同事问,为什么他们的测试运行得更早,因为她唯一正确添加的东西,我可以说是抽象类中的虚拟析构函数!坦白说,我不知道,我希望有人能启发我 我已经调试了应用程序,在从IFooListener中删除Destructor声明后,应用程序运行良好,vftable指针指向具体类的vftable。因此程序运行,prin

我的一个同事问,为什么他们的测试会失败。异常描述是纯虚拟调用。快速查看下面一个过于简化的示例代码表明,他们的测试引用了在其作用域过期后在堆栈上创建的对象,因为它们保留了过时的裸指针。简单

然后我的同事问,为什么他们的测试运行得更早,因为她唯一正确添加的东西,我可以说是抽象类中的虚拟析构函数!坦白说,我不知道,我希望有人能启发我

我已经调试了应用程序,在从IFooListener中删除Destructor声明后,应用程序运行良好,vftable指针指向具体类的vftable。因此程序运行,printfs打印。一旦虚拟析构函数进入,在对象退出其作用域后,vftable指针将指向抽象类的vftable

我还尝试了一个非虚拟的析构函数,结果是一样的——应用程序崩溃并烧毁了,就像它应该做的那样

我该如何理解这一点? 如果析构函数(虚或非虚)未在抽象类中声明和定义,则不会生成并调用基类析构函数来清理vftable

使用VS2008编译器在Windows下进行测试和调试

下面是对麻烦代码的简化重构:

#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在超出范围时会调用自己的析构函数,因为编译器已经知道要销毁的对象的确切类型!因为它知道确切的类型,所以不需要使用虚拟析构函数来清理子实例


它在一个案例中崩溃而没有崩溃的原因请注意:我并不是说它之所以有效是因为它不起作用——结果似乎只是起作用,因为这两个选项都是未定义行为的合法示例。实际上,这是因为处理抽象析构函数调用的代码与非抽象调用的代码不同。

这是一种未定义的行为,因此任何事情都可能影响结果。不值得花太多时间去推理。我不清楚你到底尝试了什么,所以我只能猜测是什么让你困惑。无论如何,第一件事是在破坏过程中,对象改变了它的类型,类似于构造。这意味着在某种程度上,动态类型是抽象基类的类型,包括对有效虚函数的必要调整。第二件事是编译器可以省略遵循“仿佛”规则的任何内容。特别是,当做某件事导致未定义的行为时,它可以假设这不会发生,就像在对象被销毁后访问对象一样。@juanchopanza试图在对象超出范围后使用它显然会产生未定义的行为,这一点很清楚。但是它是从抽象类派生出来的,没有明确的析构函数声明,足以进入未定义的领域吗?好吧,在修复悬吊指针问题之后,你是否尝试过没有虚拟析构函数?如果我处理悬空指针问题,就没有什么可考虑的了。你可能是对的,这不值得深入研究,我只是好奇为什么它工作,而它显然不应该。我确切地知道为什么程序崩溃。我想知道,当显式声明的析构函数从抽象基类定义中移除时,它为什么会工作。