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


鉴于此应用程序:

#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

实例

获得我预期输出的唯一方法是编译 -fno-elide-constructors (

我认为如果这样做会影响观察到的行为,编译器可能不会忽略,但是GCC,clang和MSVC似乎正在这样做。

我是否缺少一些通用规则或者是否特定于临时对象初始化?


3302
2018-02-08 13:18


起源

欺骗/相关 这个 和/或 这个 - NathanOliver
也, X b = 2; 是初始化,并且从不使用赋值运算符。 - Bo Persson


答案:


即使忽略副作用,也允许复制省略:

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

一个很好的一般规则是不要编写依赖于复制/移动构造函数副作用的代码,因为你很容易被elision咬掉。在C ++ 17中尤其如此,其中复制省略的某些情况是强制性的。


9
2018-02-08 13:21



除此之外,12.8.31.3:“当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动“ - rustyx


去引用 标准 12.8.3:

当满足某些标准时,允许省略实现   类对象的复制/移动构造, 即使是构造函数   选择用于复制/移动操作和/或析构函数   对象有副作用。

(强调我的)

这意味着即使副本具有副作用,也允许编译器删除副本。这正是您案件中发生的事情。


4
2018-02-08 13:21



在C ++ 17草案中它是12.8.3。我问的是C + 11。但我发现它,12.8.31。 - rustyx
@RustyX是的,它是(某种)在同一部分,两者都在 12.8.3X - Sombrero Chicken