使用聚合初始化来初始化基类子对象时复制省略

问题描述 投票:0回答:1

在下面的代码中,

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 年前发布,但是

  • 这个问题询问聚合初始化的特殊性,这里没有涵盖;
  • 正如我们所见,在上面的示例中,三分之二的经过测试的现代编译器应用了复制省略。是由于这些编译器中的错误(4 年后仍然存在)还是允许它们这样做?
c++ aggregate language-lawyer copy-elision
1个回答
0
投票

在您的代码中,我认为您会在编译器之间看到不同的行为,因为每个编译器在聚合初始化期间初始化基类时如何处理复制省略。

快速分解:

  • GCC 和 MSVC 正在跳过临时对象创建部分(感谢复制省略),这就是为什么您会看到更少的构造函数调用。他们有点走捷径,假设在制作

    A
    时不创建和移动临时
    B
    是可以的。

  • Clang按照书本进行额外的移动步骤,表明它正在谨慎行事并严格遵守规则(或者至少是它对规则的解释)。

C++ 标准为编译器提供了一些余地,可以跳过不必要的步骤来加快速度(这对您来说就是复制省略!),但对于这种具体情况还不是很清楚。这就是为什么编译器可能不会就该做什么达成一致。

很难选择立场,因为每个编译器都尽力遵循 C++ 标准,但它们对字里行间的理解有点不同。该标准没有明确针对这种具体情况制定法律,因此存在解释的回旋余地。

如果您希望您的代码在所有编译器上都能按预期运行,请尽量不要依赖这些怪癖。由于行为可能会有所不同,因此进行一些更简单的初始化可能会为您省去一些麻烦。

© www.soinside.com 2019 - 2024. All rights reserved.