为什么输出与我的期望不同?

时间:2016-08-15 03:14:36

标签: c++ c++11

我有一个像这样的简单程序:

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
    class B {
    protected:
        int     data = 0;
    public:
        B() { cout << "B() ctor\n";}
        virtual ~B() { cout << "~B()\n"; }
        virtual void method() { cout << "data in B: " << data << "\n"; }
    };

    class A : public B 
    {
        int dataA = 2;
    public:
        A() { cout << "A() ctor\n"; }
        ~A() { cout << "~A()\n"; }
        void method() { cout << "data in A: " << dataA << "\n"; }
    };

    {
        B* fptrList[]{ &B{}, &A{}};
        for (auto& itr : fptrList)
            itr->method();
    }

    cin.get();
    return 0;
}

这是我期望的结果:

B() ctor
B() ctor
A() ctor
data in B: 0
data in A: 2
~A()
~B()
~B()

以下是我运行此程序时的实际结果:

B() ctor
~B()
B() ctor
A() ctor
~A()
~B()
data in B: 0
data in B: 0

我的问题是:

  • 为什么输出与我的期望不同?
  • 在调用~A()和~B()之后如何调用method()?
  • 为什么B类的方法()会被调用两次?

1 个答案:

答案 0 :(得分:0)

好的,这是未定义的行为,但问题仍然很有趣,为什么这个未定义的行为。

  • 为什么按此顺序调用构造函数/析构函数?如前所述,创建一个接一个地创建/销毁的临时对象。

  • 为什么我可以调用已经不存在的对象的方法?你的临时对象存在于堆栈中,因此内存只会在main的末尾释放功能,因此您仍然可以访问此内存,并且不会因调用其他功能(例如打印到终端)而受到破坏。如果您使用new创建对象,而不是删除它而不是尝试使用它 - 可能会更高,系统已经回收了此内存并且您会收到分段错误。

    < / LI>
  • 为什么我看到B调用方法的2倍?这个很有趣。为了调用对象的虚函数,编译器将决定应该调用哪个方法的决策委托给虚拟表(它的地址占据了这个对象的前8个字节(至少对于我的编译器和64位))。关于虚方法的一个众所周知的细节是,在析构函数调用期间,所有虚拟方法都被调用,就好像它们不是虚拟方法一样。但是你的代码有什么用呢?您会看到它的副作用:通过使用当前类的虚拟表覆盖当前对象的虚拟表,可以在析构函数中确保非虚拟行为。因此,在调用B的析构函数之后,内存中包含您可以看到的类B的虚拟表,因为B::method被调用了两次。

让我们跟踪程序中虚拟表的值:

  1. 调用A{}:首先调用超类B - 构造函数 - (尚未完全完成的)对象具有类B的虚拟表(此地址被移入对象占用的前8个字节),而不是A - 构造函数被调用 - 现在该对象具有类A的虚拟表。
  2. ~A()的调用:执行后,A的析构函数会自动调用B的析构函数。 B的析构函数所做的第一件事就是使用B类的虚拟表覆盖对象的虚拟表。
  3. 因此,在破坏之后,内存仍然存在并被解释为对象将具有类B的虚拟表。
  4. itr->method();在地址B处找到类itr的虚拟表,并指向B::method()