虚函数的意外行为?

时间:2011-07-12 05:02:08

标签: c++ visual-studio-2010 inheritance virtual

当我使用Visual Studio 2010运行以下C ++代码时,如果任何派生类函数声明为虚拟,则程序会停止删除变量。有人可以解释一下吗?

void testInheritance()
{
    class a
    {
        public :
            char x;
            void fn1()
            {
                std::cout<<"\n In Class A Function 1 : "<<x;
            }
            virtual void fn2()
            {
                std::cout<<"\n In Class A Function 2 : "<<x;
            }
            a()
            {
                x='A';
                std::cout<<"\n In A() : "<<x;
            }
            ~a()
            {
                std::cout<<"\n In ~A : "<<x;
            }
    };

    class b: public a
    {
        public :
            char y;
            virtual void fn1()
            {
                std::cout<<"\n In Class B Function 1 : "<<y;
            }
             void fn3()
            {
                std::cout<<"\n In Class B Function 3 : "<<y;
            }
            b()
            {
                y='B';
                std::cout<<"\n In B() : "<<y;
            }
            ~b()
            {
                std::cout<<"\n In ~B : "<<y;
            }
    };

    a* var = new b();
    delete var;
}

更多信息:

我理解为了调用b :: fn1和类b的析构函数,我需要在基类中声明它们是虚拟的,即类a。但是,如果我不这样做,甚至不将类b中的任何函数(也不是来自类a)声明为虚拟,它应该调用a的fn1和析构函数,并且这完全发生。但是,当我声明b(但不是a)的任何成员是虚拟的,无论是新成员还是重载成员,然后它在使用VS2010编译时挂起并在使用gcc4.4.4在linux上编译时中断。它应该调用任一个析构函数并正常工作,但我无法理解为什么程序会中断的原因。

此外,在Visual Studio 2010中使用Intellitrace时,我尝试在挂起代码时破坏代码,我收到以下消息:

该过程似乎已死锁(或未运行任何用户模式代码)。所有线程都已停止。

5 个答案:

答案 0 :(得分:4)

您期待意外行为,因为您在程序中创建了未定义行为

使用指向derived类的base类删除non-virtual destructor类对象,结果为Undefined Behavior。未定义的Beahavor意味着任何事情都可能发生。

C ++标准第1.3.24节规定:

允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的文档方式执行,终止翻译或执行(发出诊断信息)。

如何解决问题?

Base类中的析构函数应该是虚拟的。

答案 1 :(得分:2)

您的析构函数不是虚拟的,您是否不允许delete var作为基类指针。根据其他虚拟函数的存在,您很可能只有两组行为。

答案 2 :(得分:2)

你需要声明析构函数virtual

答案 3 :(得分:1)

如果“卡住”表示未调用b::~b(),则答案是a::~a()需要virtual

您正在使用基类(a)指针来保存class b的对象。当您delete var;时,它仅调用a::~a(),而不是virtual;通过制作virtual;按正确顺序调用析构函数ab

[注意:另一种方式它只能被击中,如果你在某个地方放置了一个断点并且你没有踩过去。 :)]

答案 4 :(得分:0)

我实际上厌倦了在C ++测试中看到,询问在这种情况下的行为是什么。他们希望你回答它会调用A的析构函数而不是B的析构函数。

这不是保证的行为,你不能依赖它。未定义的行为意味着您无法确定将会发生什么,这就是这种情况。

它也是“只是不要这样做......”的一个例子。在我上一份工作中,我完全从测试此行为的系统中删除了一个测试,理由是它不相关且不合适。

使a虚拟析构函数的另一种选择是保护它。这也将保护您main()无法编译,因为您无法从那里调用delete var。您甚至无法通过b执行与main相同的操作来调用delete中未定义的行为,因为您可能会感到惊讶,但a* boost::shared_ptr<a>( new b ); 也无法访问a

{{1}}

安全,因为它会为b而不是a创建一个删除器。

由于{{1}}中还有另一个虚函数,但你几乎肯定会选择使其析构函数为虚拟函数。