我不理解clang和GCC相对于C ++ 14中的[class.copy] / 9获得的结果。

时间:2015-08-06 20:00:02

标签: c++ language-lawyer copy-constructor c++14 move-constructor

可以在下面的代码段中看到,调用用户声明的移动构造函数来初始化类型y的对象zX。请参阅live-example

#include <iostream>
struct X{
    X(){}
    X(X&&) = default;
//  X(X&);
//  X& operator=(const X& x);
//  ~X() = default;
};
X f() { X x; return x; }
X y = f();
X z = X();
int main(){}

现在,如果我们注释掉这个移动构造函数的声明,代码将继续执行而没有问题,使用隐式声明的移动构造函数。这没关系。

#include <iostream>
struct X{
    X(){}
//  X(X&&) = default;
//  X(X&);
//  X& operator=(const X& x);
//  ~X() = default;
};
X f() { X x; return x; }
X y = f();
X z = X();
int main(){}

现在,让我们在复制构造函数X(X&);的声明下面取消注释。正如我们在下一个live-example中看到的那样,代码无法编译,因为编译器不会隐式生成移动构造函数。根据§12.8/ 9(N4140)的要点(9.1),这是正确的

#include <iostream>
struct X{
    X(){}
//  X(X&&) = default;
    X(X&);
//  X& operator=(const X& x);
//  ~X() = default;
};
X f() { X x; return x; }
X y = f();
X z = X();
int main(){}

问题似乎从现在开始,当我们再次注释复制构造函数的声明并取消注释复制赋值运算符的声明时(见下文)。现在我们有一个用户声明的复制赋值运算符,根据§12.8/ 9中的项目符号(9.2),代码不应该编译。但它确实如此,在clang和GCC。

#include <iostream>
struct X{
    X(){}
//  X(X&&) = default;
//  X(X&);
    X& operator=(const X& x);
//  ~X() = default;
};
X f() { X x; return x; }
X y = f();
X z = X();
int main(){}

当我们再次注释复制赋值运算符的声明并取消注释默认析构函数的声明时,也会发生同样的情况。同样,根据§12.8/ 9中的要点(9.4),代码不应编译,但它在clang和GCC中。

我可能在这里遗漏了一些东西,但我不知道在上面的阐述中我在哪里失败了。

2 个答案:

答案 0 :(得分:2)

代码在最后一个样本中编译,不是因为任何隐式移动构造函数或移动赋值,而是因为生成了表单的复制构造函数;

X(X const&);

哪个快乐地与临时结合。

在你的第三个例子中,代码无法编译(与移动无关),因为你有一个复制构造函数X(X&);,但它是一个不合适的形式来绑定到临时,它需要是{ {1}}。临时工可以绑定到const引用。

我相信编译器是正确的。

答案 1 :(得分:1)

此代码:

#include <iostream>
struct X{
    X(){}
    X& operator=(const X& x);
};
X f() { X x; return x; }
X y = f();
X z = X();
int main(){}

非常好。 [class.copy]确实说:

  

如果类X的定义没有显式声明移动构造函数,则隐式地使用非显式构造函数   声明为默认当且仅当[...] X没有用户声明的复制赋值运算符时,[...]

因此,X将没有隐式默认的移动构造函数。但请注意,备选方案不是已删除的移动构造函数。简直是一个不存在的。如果删除了隐式声明的移动构造函数,则代码将无法编译。但这不是这种情况。

但它对 copy 构造函数有什么看法?

  

如果类定义没有显式声明复制构造函数,则隐式声明非显式构造函数。   如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的副本   构造函数定义为已删除;否则,它被定义为默认值(8.4)。如果是,则不推荐使用后一种情况   该类具有用户声明的复制赋值运算符或用户声明的析构函数。

X既没有移动构造函数也没有移动赋值运算符,因此X具有隐式声明的默认复制构造函数。这种情况已弃用,但仍然是有效的代码。很可能在未来的某个标准中,它会很好地形成,但还没有!

因此,我们的X确实是:

struct X{
    X(){}
    X(const X&) = default;
    X& operator=(const X& x);
};

和表达式X y = f();复制初始化y。它无法移动初始化y,因为我们没有移动构造函数,但复制构造函数仍然是X的匹配项。