为什么强制复制省略不适用于潜在重叠的子对象?

use*_*751 8 c++ language-lawyer

背景

许多 C++ 程序员可能已经熟悉强制省略复制/移动操作。从链接文本复制的示例。编译器不得插入复制和移动:

  • 在对象的初始化中,当初始化表达式是与变量类型相同的类类型(忽略 cv 限定)的纯右值时:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Run Code Online (Sandbox Code Playgroud)

这仅适用于已知正在初始化的对象不是潜在重叠子对象的情况:

struct C { /* ... */ };
C f();
 
struct D;
D g();
 
struct D : C
{
    D() : C(f()) {}    // no elision when initializing a base-class subobject
    D(int) : D(g()) {} // no elision because the D object being initialized might
                       // be a base-class subobject of some other class
};
Run Code Online (Sandbox Code Playgroud)

在实践中,这是通过提供f一个隐藏的额外参数来告诉它在哪里构造其返回值来实现的。

问题

为什么潜在重叠的子对象不受强制省略?没有理由f不能直接在 的基类部分构造其返回值D

Col*_*mbo 2

相关资源有

有几点:

  1. f()将调用完整对象 (C1) 构造函数,而不是基础对象 (C2) 构造函数。这立即排除了C具有虚拟碱基的情况下的复制省略(在这种情况下 C1 != C2)。
  2. 在其他情况下,为了避免复制(实现),C1 需要无条件地保持尾部填充不变,除非C是 POD(在 Itanium 的情况下)。然而,这是不切实际的保证,因为有时机器指令在包含填充时更有效,存在 ABI 考虑因素等。

你的反对意见

D的成员还没有初始化

并不完全正确,因为虚拟基类是在完整对象的末尾分配的:

struct A {
    char i = 5;
    A() {} // to prevent this being a POD for the purpose of layout
};
struct C {
    int j; char s;
    C() { /* ...*/ }
};
struct D : virtual A, C {
    D() : A(), C(f()) {}
};
Run Code Online (Sandbox Code Playgroud)

说到这里,记忆中A立刻浮现出C。当稍后为 f 或 C 的向量生成代码时,我们无法知道 C 是否将成为具有虚拟基的完整对象的基。如果我们让f构造基对象(在构造之后发生)A,我们可能会覆盖重复使用的尾部填充,即所在i的位置。

我建议阅读上面第二个链接中的讨论,因为它还解决了切线点以及可以更改措辞以反映当前实施实践的方式。