虚拟析构函数在特定情况下的实际必要性

时间:2010-12-06 09:57:48

标签: c++ virtual-destructor

C ++ 03 5.3.5.3

  

在第一种选择中(删除   对象),如果是静态类型的   操作数与其动态不同   类型,静态类型应为基础   操作数的动态类型的类   而静态类型应该有一个   虚拟析构函数或行为是   未定义。

这是理论。然而,问题是一个实际的问题。如果派生类没有添加数据成员怎么办?

struct Base{
   //some members
   //no virtual functions, no virtual destructor
};
struct Derived:Base{
   //no more data members
   //possibly some more nonvirtual member functions
};

int main(){
     Base* p = new Derived;
     delete p; //UB according to the quote above
}

问题: 是否存在真正危险的现有实施? 如果是这样,你能否描述一下如何在该实现中实现内部代码,使代码崩溃/泄漏或其他什么?我求你你相信,我发誓我无意依赖这种行为:)

5 个答案:

答案 0 :(得分:6)

一个示例是,如果您在operator new中提供自定义struct Derived。显然,调用错误的operator delete可能会产生毁灭性的结果。

答案 1 :(得分:1)

我知道没有任何实施上述危险,我认为不可能有这样的实施。

原因如下:

“未定义的行为”是一个包罗万象的短语意思(众所周知),任何事情都可能发生。代码可以吃你的午餐,或者什么也不做。

但是,编译器编写器是理智的人,编译时未定义的行为与运行时的未定义行为之间存在差异。如果我为上面的代码片段很危险的实现编写编译器,在编译时很容易捕获和阻止。我可以说这是编译错误(或警告,也许): Error 666: Cannot derive from class with non-virtual destructor.

我认为我可以这样做,因为在这种情况下编译器的行为是由标准定义的而不是

答案 2 :(得分:0)

我无法回答特定的编译器,你必须要求编译器编写者。即使编译器现在可以工作,也可能在下一个版本中没有这样做,所以我不会依赖它。

你需要这种行为吗?

让我猜一下

  1. 您希望能够拥有基类指针而无需查看派生类和
  2. Base中没有v表,
  3. 能够清理基类指针。
  4. 如果这些是您的要求,可以使用boost :: shared_ptr或您自己的改编。

    在你传递指针的那一刻,你传递了一个boost :: shared_ptr,下面是一个实际的“Derived”。当它被删除时,它将使用在创建指针时使用正确删除创建的析构函数。为了安全起见,你应该给Base一个受保护的析构函数。

    请注意,仍然存在v-table,但它位于共享指针删除器基础中,而不在类本身中。

    要创建自己的改编,如果使用boost :: function和boost :: bind,则根本不需要v-table。你只需要使用boost :: bind来包装底层的Derived *,并在函数上调用delete。

答案 3 :(得分:0)

在您的特定情况下,如果您没有在派生类中声明任何数据成员,并且您没有任何自定义新/删除操作符(如Sharptooth所述),您可能没有任何问题,但是保证没有用户会派出你的课程?如果不使Base's析构函数为虚拟,则Derived派生的任何类都无法调用其析构函数,以防通过Base使用派生类的对象指针。

此外,有一个普遍的概念,即如果你的基类中有虚函数,那么析构函数应该是虚拟的。所以最好不要惊讶任何人:)

答案 4 :(得分:0)

我完全赞同'罗迪'。

除非您正在编写为不存在的虚拟机设计的变换编译器的代码,只是为了证明所谓的未定义的行为可以咬人 - 这没有问题。

关于自定义new/delete运算符的'sharptooth'这一点在这里不适用。因为虚拟世界并不会以任何方式解决他/她描述的问题。

然而,这是一个好点。这意味着您提供虚拟设备的模型以及启用多态对象创建/删除的模型在设计上存在缺陷。 更正确的设计是为这些对象配备一个可以同时执行两项操作的虚函数:调用其(正确的)析构函数,并以释放它的方式释放其内存。简单来说 - 用对象本身已知的适当方法破坏对象。