众所周知,在某些条件下编译器可能会省略对复制构造函数的调用。然而,标准明确表示编译器只能自由更改运行时行为(调用或不调用复制构造函数),但翻译过程中仍需假定调用了复制构造函数。特别地,编译器会检查是否存在可以调用的有效复制构造函数。
我遇到一种情况,析构函数调用可能被省略,但编译器在是否需要存在有效析构函数方面存在不同。
以下是一个完整的示例,展示了此问题可能发生的情况以及编译器行为的差异。
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>
的默认构造函数是平凡的(不会抛出异常),根据as-if规则,编译器可能简化代码为:
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
是不完整的类型,无法获取其大小。
main
中创建一个A<B>
,gcc将拒绝编译。 - zneakB
完成的 TU 中定义 - 在这种情况下,获取其大小不会出错。 - Alan Stokes