在初始化中省略非平凡的复制/移动构造函数是否合法?

11

鉴于这个应用程序:

#include <iostream>

struct X {
  X(int _x)                   { x = _x     + 1; }
  X(const X& that)            { x = that.x + 10; }
  X& operator=(const X& that) { x = that.x + 100; return *this; }
  X(X&& that)                 { x = that.x + 1000; }
  X& operator=(X&& that)      { x = that.x + 10000; return *this; }
  int x;
};

int main() {
  X a(1);
  std::cout << "a.x=" << a.x << std::endl;
  X b = 2;
  std::cout << "b.x=" << b.x << std::endl;
  X c = X(3);
  std::cout << "c.x=" << c.x << std::endl;
  X d = a;
  std::cout << "d.x=" << d.x << std::endl;
}

我希望得到的输出结果是:

a.x=2
b.x=1003
c.x=1004
d.x=12

我得到的结果却是:

a.x=2
b.x=3
c.x=4
d.x=12

Live example

只有使用-fno-elide-constructors编译才能得到我期望的输出结果(示例)。

我曾认为,如果这样做会影响观察到的行为,编译器可能不会省略掉一些内容,但 GCC、clang 和 MSVC 似乎正在这样做。

我是错过了某些通用规则,还是这仅适用于使用临时对象初始化的情况?


重复/相关于这个和/或这个的问题。 - NathanOliver
另外,X b = 2; 是初始化,无论如何都不使用赋值运算符。 - Bo Persson
2个回答

9

即使忽略副作用,复制省略也是允许发生的:

[class.copy]/31: 当满足某些条件时,实现可以省略类对象的复制/移动构造函数,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。[...]

一个好的通则是不要编写依赖于复制/移动构造函数副作用的代码,因为你很容易被省略所咬伤。这在C++17中特别适用,因为某些情况下复制省略是强制性的。


3
此外,根据12.8.31.3规定:“当一个未绑定到引用的临时类对象要被复制/移动到与其cv限定类型相同的类对象中时,可以通过直接在省略的复制/移动操作的目标中构造临时对象来避免执行复制/移动操作。” - rustyx

4
根据12.8.3章节标准的引述:
当满足某些条件时,即使所选用的构造函数和/或对象的析构函数具有副作用,实现也可以省略类对象的复制/移动构造。
这意味着即使复制具有副作用,在您的情况下编译器仍然可以省略复制。

这是C++17草案中的12.8.3。我之前在问C++11的问题,但我找到了它,就在12.8.31。 - rustyx
@RustyX 是的,它(有点)在同一部分中,都在 12.8.3X 中。 - Hatted Rooster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接