2017-06-22 63 views
-2

让我们一个C++例如:C++ vtable究竟是如何工作的? (与实施例中的Q值。)

class A 
{ 
public: 
    A() { cout << "hey" << endl; } 
    ~A() { cout << "by" << endl; } 


}; 


class B : public A 
{ 
public: 
    B() {} 
    virtual ~B() { cout << "from b" << endl; } 

}; 
int main() 
{ 
    A * a = new B[5]; 



    delete[] a; 

    return 0; 
} 

此代码的结果是“是”的无限循环,这是为什么? B虚表应该被upcast到没有vtable的A,所以我希望它在尝试访问虚拟构造函数时抛出一个异常。

p.s. 在哪里我可以阅读各种有线构造函数析构函数的行为举例? (附范例)

+0

[何时使用虚拟析构函数?](https://stackoverflow.com/questions/461203/when-to-use-virtual-destructors) – CoryKramer

+1

@CoryKramer我不认为这是完全重复的,OP不理解为什么只在派生类中创建虚拟驱动器不起作用。 – Slava

+0

欢迎来到Stack Overflow。请花些时间阅读[The Tour](http://stackoverflow.com/tour),并参阅[帮助中心](http://stackoverflow.com/help/asking)中的资料,了解您可以在这里问。 –

回答

3

将一个B的数组作为A的数组删除是未定义的行为。这是真的,只要A有一个非平凡的析构函数(当它有一个微不足道的析构函数,它可能被定义,我不记得)。虚拟的继承 - 无论在这种情况下它的不确定性都很重要。

之后的所有内容都比通过电子邮件发送到您的联系人列表中的硬盘映像文件和浏览器密码缓存更好,这是C++标准下“未定义行为”的合法示例。你好幸运啊。


特别是,我猜A是一个平凡的对象。并且delete[]正期望删除一系列大小为1的平凡物体。不知何故B是更大和不平凡的事实(它包含一个vtable)已经弄乱了你的编译器,导致你的无限循环。

也许它存储关于如何删除数组的信息的格式与使用非虚拟析构函数的普通对象不同。用vtable的情况下,也许它在那里存储一个函数指针,而在平凡的情况下它存储一个计数。

环路然后不是无限的,而是迭代(几乎随机32或64位号码)倍,作为指针值趋于相对随机的。

如果你真的关心什么具体的未定义行为的代码导致对这个特定的编译器的这个特殊的系统在这个特别的版本,这个特殊的环境中,可以godbot你的代码。但我不明白你为什么要关心。


C++的vtables仅用于一种类型的如果需要该类型创建。继承和添加virtual不会改变类型确实或不需要vtable的事实。

C中的原始数组是而不是逆变或协变。如果你有一个数组,你不能安全它转换成一个指向任何不同类型的(也有一些涉及到标准的布局和原始字节/字符等的极窄的例外)。

如果我们回到你的例子并删除阵列:

A * a = new B; 

delete a; 

这得到那么糟糕。删除a仍然是UB,因为您正在删除B作为A

没有virtual ~A()A中,您不能删除B作为A

为了解决这个问题,我们添加:

virtual ~A() { cout << "by" << endl; } 

现在A有虚表 - 的A例如携带一个指向虚函数表,其中(除其他事项外)告诉编译器如何删除A或派生的A类型。

现在代码是明确界定,并印刷

hey 
from b 
by 

如果我的头,编译器是正确的。

+0

我知道如何解决它,这是我不明白的有线终端的情况下,我知道它应该做的内存泄漏,但为什么会出现一个循环???为什么会发生? – joy

+0

@joy你做了未定义的行为。这是第1段。那是“为什么有一个循环”。未定义的行为意味着*任何事情都可能发生*。无限循环是“任何事物”的一个子集,所以它完全解释了循环的“为什么”。现在,我还解释了为什么编译器可能会输出一个看起来无限循环(它将一个指针解释为另一个指针)的可能性。什么不清楚? – Yakk

0

这段代码的结果是一个“by”的无限循环,为什么这样?

因为基类A没有应该上溯造型到虚析构函数

乙虚函数表没有虚函数表

它不工作的方式。当此代码:

delete [] a; 

编译,编译器使用的classA定义,没有虚函数表的。事实上,派生类有vtable在这里并不重要。

所以我会希望它会抛出一个异常,当他试图达到虚拟构造函数。

你的期望是错误的。首先,没有虚拟构造函数这样的东西。其次 - 在这种情况下,您使用A类(通过A*)。如果派生类有vtable或不在这种情况下无关紧要。

注意:C++语言不会规定虚拟函数解析必须通过vtable完成,尽管这样实现它很常见。我用这个词是因为你提出了问题。