【问题标题】:Unexpected behaviour of virtual function?虚函数的意外行为?
【发布时间】:2011-07-12 05:02:08
【问题描述】:

当我使用 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 类)中的任何函数声明为虚拟函数,它应该同时调用 fn1 和 a 的析构函数,并且这完美地发生了。但是,当我将 b(但不是 a)的任何成员声明为虚拟成员时,无论是新成员还是重载成员,它都会在使用 VS2010 编译时挂起,而在 Linux 上使用 gcc4.4.4 编译时会中断。它应该调用了任何一个析构函数并正常工作,但我无法理解程序中断的原因。

在 Visual Studio 2010 中进一步使用 Intellitrace 时,我尝试在代码挂起的地方中断代码,我收到以下消息:

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

【问题讨论】:

  • 还要注意a* bb = new b; bb-&gt;fn1(); 将调用a::fn1,因为您忘记在a 中的fn1 声明之前放置virtual 关键字。

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


【解决方案1】:

您会出现意外行为,因为您在程序中创建了未定义行为

使用指向具有non-virtual destructorbase 类的指针删除derived 类对象会导致Undefined Behavior。未定义的行为意味着任何事情都可能发生。

C++ 标准第 1.3.24 节规定:

允许的未定义行为的范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式(有或没有发出诊断消息)的行为,到终止翻译或执行(发出诊断消息)。

如何解决问题?

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

【讨论】:

  • 感谢 Matthieu M. 的信息,但如果我没有将 b 类中的任何函数声明为虚拟函数,它会调用 a 的析构函数并正常工作而不会出现任何卡住/崩溃。我可能希望将此函数虚拟化以进一步派生类,而不是从 a 到 b。在这种情况下,它不应该崩溃/阻塞。
【解决方案2】:

您的析构函数不是虚拟的,您是否不允许将delete var 作为基类指针。很可能你只是得到了两组行为,具体取决于其他虚函数的存在。

【讨论】:

    【解决方案3】:

    你需要声明析构函数virtual

    【讨论】:

    • 如果我没有在 b 类中声明任何其他虚拟函数,那么它是如何工作的??
    • 在你的类中有多少虚函数并不重要。析构函数使用与其他成员函数相同的函数调用机制
    【解决方案4】:

    如果“卡住”的意思是,b::~b() 没有被调用,那么答案是,a::~a() 需要是 virtual

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

    [注意:如果您在某处放置了断点并且您没有单步执行,则只能触发另一种方式。 :)]

    【讨论】:

    • 删除指向派生类对象的多态基类指针是一种未定义的行为,因此从技术上讲,任何事情都可能发生,尽管不太可能(程序也可能卡住)。
    • 不,卡在这里意味着阻塞。为问题添加了更多信息。
    【解决方案5】:

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

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

    这也是“只是不要这样做..”的一个实例。在我的上一份工作中,我完全从系统中删除了一个测试此行为的测试,理由是它无关紧要且偏离主题。

    使a 的析构函数变为虚拟的另一种选择是使其受到保护。这也将保护您,因为 main() 将无法编译,因为您无法从那里调用 delete var。你甚至不能通过与main 相同的方式在b 中调用未定义的行为,因为你可能会感到惊讶,但deletea* 在那里也将无法访问。

    boost::shared_ptr<a>( new b );
    

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

    由于a 中有另一个虚函数,但您几乎可以肯定选择将其析构函数设为虚函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-16
      • 2019-06-13
      相关资源
      最近更新 更多