在下面的代码中,
struct B
是一个以struct A
为基数的聚合,B
-对象是聚合初始化的B b{ A{} }
:
#include <iostream>
struct A {
A() { std::cout << "A "; }
A(const A&) { std::cout << "Acopy "; }
A(A&&) { std::cout << "Amove "; }
~A() { std::cout << "~A "; }
};
struct B : A { };
int main() {
B b{ A{} };
}
GCC 和 MSVC 执行
A
复制省略,打印:
A ~A
当 Clang 创建一个临时对象并移动它时,打印:
A Amove ~A ~A
演示:https://gcc.godbolt.org/z/nTK76c69v
同时,如果定义了
struct A
并删除了移动/复制构造函数:
struct A {
A() {}
A(const A&) = delete;
A(A&&) = delete;
~A() {}
};
然后 Clang(预期)和 GCC(在复制省略的情况下不是那么预期)都拒绝接受它,但 MSVC 仍然可以接受。演示:https://gcc.godbolt.org/z/GMT6Es1fj
这里允许(甚至强制)复制省略吗?哪个编译器是正确的?
有一个相关问题为什么 RVO 不应用于基类子对象初始化? 4 年前发布,但是
在您的代码中,我认为您会在编译器之间看到不同的行为,因为每个编译器在聚合初始化期间初始化基类时如何处理复制省略。
GCC 和 MSVC 正在跳过临时对象创建部分(感谢复制省略),这就是为什么您会看到更少的构造函数调用。他们有点走捷径,假设在制作
A
时不创建和移动临时 B
是可以的。
Clang按照书本进行额外的移动步骤,表明它正在谨慎行事并严格遵守规则(或者至少是它对规则的解释)。
C++ 标准为编译器提供了一些余地,可以跳过不必要的步骤来加快速度(这对您来说就是复制省略!),但对于这种具体情况还不是很清楚。这就是为什么编译器可能不会就该做什么达成一致。
很难选择立场,因为每个编译器都尽力遵循 C++ 标准,但它们对字里行间的理解有点不同。该标准没有明确针对这种具体情况制定法律,因此存在解释的回旋余地。
如果您希望您的代码在所有编译器上都能按预期运行,请尽量不要依赖这些怪癖。由于行为可能会有所不同,因此进行一些更简单的初始化可能会为您省去一些麻烦。