编译器对析构函数省略的自由度是多少?

Cas*_*eri 14 c++ destructor language-lawyer c++11

众所周知,在某些条件下,编译器可能会忽略对复制构造函数的调用.但是,标准清楚地表明编译器只能自由地更改运行时行为(调用或不调用复制构造函数),但执行转换就像调用复制构造函数一样.特别是,编译器检查是否有一个有效的复制构造函数来调用.

我遇到了一个情况,可能会省略析构函数调用,但编译器在是否需要存在有效的析构函数方面存在差异.

这是一个完整的示例,说明了此问题可能如何发生以及编译器的行为方式有何不同.

template <typename T>
struct A {
  ~A() { (void) sizeof(T); }
};

struct B;    // defined elsewhere.

struct C {
  A<B> x, y;
  ~C();      // defined in a TU where B is complete.
};

int main() {
  C c;
}
Run Code Online (Sandbox Code Playgroud)

编译时main()编译生成C的默认构造函数.此构造函数默认首先初始化x然后再初始化y.如果在y施工期间抛出异常,则x必须销毁.生成的代码如下所示:

new ((void*) &this->x) A<B>;   // default initializes this->x.
try {
  new ((void*) &this->y) A<B>; // default initializes this->y.
}
catch (...) {
  (this->x).~A<B>();           // destroys this->x.
  throw;
}
Run Code Online (Sandbox Code Playgroud)

知道A<B>默认构造函数是微不足道的(并且不抛出),在as-if规则下,编译器可能会将代码简化为:

new ((void*) &this->x) A<B>;   // default initializes this->x.
new ((void*) &this->y) A<B>;   // default initializes this->y.
Run Code Online (Sandbox Code Playgroud)

因此,没有必要打电话~A<B>().(实际上,编译器甚至可以删除上面的两个初始化,因为它A<B>的构造函数很简单,但这对于这个讨论并不重要.)

问题是:即使可能省略对析构函数的调用,编译器是否应验证是否有可用的析构函数?我无法在标准上找到任何澄清此事的内容.任何人都可以提供相关报价吗?

如果编译器决定不翻译~A<B>()(如gcc和Visual Studio那样),那么编译就会成功.

但是,如果编译器决定转换~A<B>()(如clang和icc do),则会引发错误,因为这B是一个不完整的类型,并且无法获取其大小.

Ala*_*kes 1

我认为标准没有规定这一点。如果~A<B>被实例化,那么它的格式不正确,需要进行诊断。正如你所说,如果构造y抛出,则x必须被销毁。

然而,构造函数y永远不会抛出异常,因此可以说永远不需要析构函数的定义存在(15.2/2、14.7.1/3)。