2013-05-13 75 views
1

正在回答这个问题 - Pure virtual call in destructor of most derived class - 我尝试了一些代码来检查一些语法,并发现当调用sucessive析构函数时,它们会调用它们相关的虚函数。考虑下面的代码:为什么在析构函数中虚拟表设置回该级别?

class Base 
{ 
public: 
virtual void Method() = 0; 
}; 

class Derived : public Base 
{ 
public: 
~Derived() 
{ 
    Method(); 
} 

virtual void Method() 
{ 
    cout << "D"; 
} 
}; 

class DoubleD : public Derived 
{ 
public: 

~DoubleD() 
{ 
    Method(); 
} 

virtual void Method() 
{ 
    cout << "DD"; 
} 
}; 

int main(array<System::String ^> ^args) 
{ 
    DoubleD D; 
    DoubleD E; 
    return 0; 
} 

如所预期的,当对象被破坏时,它调用正确的方法(例如,第一最衍生,然后将第二个最导出)。

输出:DD D

我的问题是,这是为什么?既然你不打算在c'tor/d tor中调用虚函数,为什么虚表要“正确地展开”呢?

例如,我可以看到为什么最派生的工作,这是虚拟函数指针表在启动时的状态。但是为什么当调用Derived的析构函数时,该表是否正确设置为指向该类实现Method

为什么不只是离开它,或者如果它很好,请将该值设置为NULL。

+1

“将值设置为NULL”?设置*什么*值为空?!另请注意,C++中没有“vtable”。这是一个实现细节,如果你已经设法了解它,那么你可能真的很接近学习如何实现析构函数 - 为什么你停止阅读? – 2013-05-13 20:46:48

+0

在附注上,将析构函数标记为虚拟。使用虚拟功能是一种很好的做法。 – bjskishore123 2013-05-13 20:49:30

+1

“既然你不打算在c'tor/d tor中调用虚函数”:谁告诉你的?建议是要小心地打电话给他们,因为他们已经“解开”(就像你所说的那样),可能不会达到你的期望。 (如果它们是纯虚拟的,不要打电话给他们;这是不允许的。) – 2013-05-13 21:17:20

回答

0

在创建对象的初始设置后,虚拟表在运行时不会被修改。 在某些实现中,应根据类的基础创建虚拟表。

在您的示例中,当DoubleD对象被销毁时,它会调用DoubleD类中的方法函数,因为该对象的DoubleD部分尚未完全销毁。 DoubleD类的VTable有一个方法函数指向其类中的方法,因为它被覆盖(在最后一级继承中)

一旦DoubleD被销毁,现在对象类型的类型是Derived。所以这个调用必须去Derived类的vtable中的方法。因此,行为。

+0

哦,哇,所以如果你有一个长的继承链,同一个函数被重复覆盖,对象的大小实际上更大,因为每个类都有它自己的v-表的副本?这是为什么? (纯粹是为了处理这种情况吗?似乎是OTT,只是将其定义为未定义的行为) – 2013-05-13 21:06:02

+2

@ T.Kiley:不,在大多数实现中,每个_class_都有一个表,但该类的所有实例/副本_share_单个表。通常,每个实例/副本都有一个指向当前最派生类型的表的指针,并且在“展开”时它将指针更改为下一个表。 – 2013-05-13 21:13:33

+1

@ T.Kiley:对象的大小取决于实现。大多数实现通常使用这种“每班vtable”的概念。所有的对象将有v-ptr到一个共享的v-表(该类) – bjskishore123 2013-05-13 21:17:01

2

行为完全明确。你不应该担心你的编译器厂商如何设法实现它(尽管你自己想出来并不是很难,或者只是查找)。

通常不是建议由于非直观行为而在析构函数中调用虚函数,但没有什么根本错误。

5

由于您不打算在c'tor/d'tor中调用虚拟函数,因此为什么虚拟表格要“正确地展开”?

前提是错误的。从构造函数或析构函数调用虚函数并没有什么错,只要你知道它们是如何工作的。如您所见,动态类型是正在运行的构造函数或析构函数的类型,因此您不会对尚未构建或已销毁的对象部分进行虚拟调用。

+0

我认为,因为大家都说不要在构造函数/析构函数中调用它是未定义的行为,所以我猜应该检查这样的事情! – 2013-05-13 21:06:57

+0

@ T.Kiley:一般来说,如果X不被允许,就不会说“不要做X”。如果不允许X,可以说“X不是有效的C++”。关于做什么和不做什么的建议只涉及在语言中有效的事情。这隐含地认为你绝不能做那些不被允许的事情。 – 2013-05-13 21:16:57

+0

@ T.Kiley:我不知道谁是“每个人”,但他们错了。如果函数是纯虚函数,那只有未定义的行为。 – 2013-05-13 21:18:57

1

这是如何按照标准工作的。

至于为什么,在运行派生类的析构函数之后,不能指望该类的任何属性有效或一致。如果调用其中一个虚拟方法,如果它进入派生类方法,将会是一场灾难。

编译器很可能会完全绕过vtable,因为它已经知道哪个重写方法适用于对象的当前状态。这只是一个实现细节。

相关问题