0

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
}

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

4

5 回答 5

6

一个示例是,如果您operator newstruct Derived. 显然,说错话operator delete可能会产生毁灭性的后果。

于 2010-12-06T10:06:55.440 回答
1

我知道没有任何实施会导致上述危险,而且我认为不太可能会有这样的实施。

原因如下:

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

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

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

于 2010-12-06T10:27:08.527 回答
0

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

你需要这种行为吗?

让我猜猜

  1. 您希望能够在不查看派生类的情况下拥有基类指针,并且
  2. Base 中没有 v-table 和
  3. 能够清理基类中的指针。

如果这些是您的要求,则可以使用 boost::shared_ptr 或您自己的改编。

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

请注意,仍然有一个 v-table,但它位于共享指针删除器库中,而不是在类本身中。

要创建自己的适配,如果您使用 boost::function 和 boost::bind,则根本不需要 v-table。你只需让你的 boost::bind 包装底层的 Derived* 和函数调用 delete 就可以了。

于 2010-12-06T10:24:17.417 回答
0

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

此外,还有一个普遍的概念是,如果您的基类中有虚函数,则应该将析构函数设为虚函数。所以最好不要让任何人感到惊讶:)

于 2010-12-06T11:15:10.280 回答
0

我完全同意“罗迪”。

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

关于自定义运算符的“尖牙”点在new/delete这里不适用。因为 virtual d'tor 不会以任何方式解决他/她描述的问题。

不过,这是一个很好的观点。这意味着您提供虚拟 d'tor 并通过这种方式启用多态对象创建/删除的模型在设计上是有缺陷的。更正确的设计是为此类对象配备一个虚拟函数,该函数同时执行两件事:调用其(正确的)析构函数,并以应释放的方式释放其内存。简而言之 - 通过适当的手段摧毁对象,这些手段以对象本身而闻名。

于 2010-12-06T11:21:47.703 回答