什么's编译器's析构函数省略的自由

What's the compiler's freedom for destructor elision?

本文关键字:自由 析构函数省 什么 编译器      更新时间:2023-10-16

众所周知,在某些条件下,编译器可能会取消对复制构造函数的调用。然而,该标准明确指出,编译器只有更改运行时行为的自由(调用或不调用复制构造函数),但执行翻译时就像调用复制构造函数一样。特别是,编译器检查是否有一个有效的复制构造函数可以调用。

我遇到了这样一种情况:析构函数调用可能被忽略,但编译器在是否需要存在有效的析构函数方面存在差异。

下面是一个完整的例子,展示了这个问题是如何发生的,以及编译器的行为有何不同。

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;
}

编译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;
}

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

new ((void*) &this->x) A<B>;   // default initializes this->x.
new ((void*) &this->y) A<B>;   // default initializes this->y.

因此,不需要调用~A<B>()。(实际上,编译器甚至可以删除上面的两个初始化,因为A<B>的构造函数是微不足道的,但这对本文的讨论并不重要。)

问题是:即使对析构函数的调用可能被忽略,编译器是否应该验证有效的析构函数是否可用我在《标准报》上找不到任何澄清此事的内容。有人能提供相关报价吗?

如果编译器决定不翻译~A<B>()(就像gcc和Visual Studio所做的那样),则编译成功。

然而,如果编译器决定无论如何都要翻译~A<B>()(就像clang和icc一样),那么它就会引发一个错误,因为这里B是一个不完整的类型,不能采用它的大小。

我不认为这是标准指定的。如果~A<B>被实例化,则它是不成形的,并且需要进行诊断。正如你所说,如果构造y抛出,那么x必须被销毁。

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