虚析构函数和未定义的行为

iam*_*ind 15 c++ undefined-behavior virtual-destructor

这个问题不同于' 何时/为什么要使用virtual析构函数?".

struct B {
  virtual void foo ();
  ~B() {}  // <--- not virtual
};
struct D : B {
  virtual void foo ();
  ~D() {}
};
B *p = new D;
delete p;  // D::~D() is not called
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 这可以归类为未定义的行为(我们知道~D()不会被称为肯定)吗?
  2. 如果~D()是空的怎么办 它会以任何方式影响代码吗?
  3. 在使用new[]/ delete[]with时B* p;,~D()无论virtual析构函数是什么,都肯定不会被调用.它是未定义的行为还是定义明确的行为?

Alo*_*ave 19

何时/为什么要使用虚拟析构函数?
遵循Herb Sutters 指南:

基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的

这可以被归类为未定义的行为(我们知道~D()肯定不会被调用)?

根据标准,它是未定义的行为,这通常导致Derived类析构函数未被调用并导致内存泄漏,但是在未定义行为的效果之后进行推测是无关紧要的,因为标准没有在这方面保证任何东西.

C++ 03标准:5.3.5删除

5.3.5/1:

delete-expression运算符销毁由new-expression创建的派生程度最高的对象(1.8)或数组.
delete-expression
::: opt delete cast-expression
:: opt delete [] cast-expression

5.3.5/3:

在第一个替代(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的动态类型的基类,静态类型应具有虚拟析构函数或行为未定义.在第二个备选(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为是未定义的.73)

如果~D()是空的怎么办 它会以任何方式影响代码吗?
仍然是根据标准的未定义行为,派生类析构函数为空可能只是使您的程序正常工作,但这又是特定实现的实现定义方面,从技术上讲,它仍然是未定义的行为.

请注意,这里没有gaurantee没有使派生类析构函数virtual不会导致对派生类析构函数的调用,并且这个假设是不正确的.根据标准,一旦您在未定义的行为领域交叉,所有投注都将被取消.

请注意他标准中关于未定义行为的内容.

C++ 03标准:1.3.12未定义的行为[defns.undefined]

行为,例如在使用错误的程序结构或错误数据时可能出现的行为,本国际标准没有规定任何要求.当本国际标准忽略对行为的任何明确定义的描述时,也可能预期未定义的行为.[ 注意:允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征的文件表示(有或没有发出诊断消息),终止翻译或执行(发布诊断信息).许多错误的程序结构不会产生未定义的行为; 他们需要被诊断出来.]

如果仅调用派生的析构函数,则由上面引用中的粗体文本控制,对于每个实现显然都是开放的.

  • @iammilind:*因为保证~D()不会被称为*,所以谁说?标准只说明如果析构函数不是虚拟的那么它是UB,未被调用的析构函数是**大多数实现中的后效**并且它不是gauranteed,也不是标准所要求的. (2认同)
  • @iammilind无法保证不调用`~D()`.标准说它是*undefined*在这种情况下发生了什么,可能包括编译器以某种方式插入魔法使`~D()`被调用!它只是从v表实现开始,在大多数编译器中,不会调用派生的析构函数. (2认同)
  • 注意:5.3.5/3 在 C++11 和 C++14 中基本保持不变,因此这个答案仍然是正确的。 (2认同)

Lal*_*and 7

  1. 未定义的行为
  2. (一个第一个音符,这些deconstructors一般并不像你想象的那么空的.你仍然需要解构所有成员)即使解构是真正的空(POD?),那么它仍然取决于你的编译器.标准未定义.对于所有标准维护,您的计算机可能会破坏删除.
  3. 未定义的行为

实际上没有理由在类中继承非虚拟公共析构函数.看看这篇文章,指南#4.

使用受保护的非虚析构函数和shared_ptrs(它们具有静态链接)或公共虚拟析构函数.