我不明白在 C++14 中 clang 和 GCC 相对于 [class.copy]/9 获得的结果。
I don't understand the results obtained in clang and GCC vis-à-vis [class.copy]/9 in C++14.
在下面的代码片段中可以看到,调用了用户声明的移动构造函数来初始化类型 X
的对象 y
和 z
。参见 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 中编译。
我可能在这里遗漏了一些东西,但我只是不知道我在上面的阐述中哪里失败了。
最后一个示例中的代码编译不是因为任何隐式移动构造函数或移动赋值,而是因为生成了以下形式的复制构造函数;
X(X const&);
这很高兴绑定到临时文件。
在你的第三个示例中,代码无法编译(与移动无关),因为你有一个复制构造函数X(X&);
,但它的形式不适合绑定到临时对象,它需要是 X(X const&);
。临时对象可以绑定到 const 引用。
我相信编译器是正确的。
此代码:
#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] 确实说:
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly
declared as defaulted if and only if [...] X does not have a user-declared copy assignment operator, [...]
因此,X
将没有隐式默认的移动构造函数。但是,请注意替代方案不是 deleted 移动构造函数。简直就是一个不存在的。如果隐式声明的移动构造函数被删除,那么代码将无法编译。但这里不是这样。
但是它对 copy 构造函数说了什么?
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared copy
constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor.
X
既没有移动构造函数也没有移动赋值运算符,因此 X
有一个隐式声明的默认复制构造函数。这种情况 已弃用 ,但仍然是有效代码。很可能在未来的某个标准中它会是良构的,但现在还不是!
结果我们的X
真的是:
struct X{
X(){}
X(const X&) = default;
X& operator=(const X& x);
};
和表达式 X y = f();
复制初始化 y
。它不能移动初始化 y
,因为我们没有移动构造函数,但复制构造函数仍然匹配 X
。
在下面的代码片段中可以看到,调用了用户声明的移动构造函数来初始化类型 X
的对象 y
和 z
。参见 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 中编译。
我可能在这里遗漏了一些东西,但我只是不知道我在上面的阐述中哪里失败了。
最后一个示例中的代码编译不是因为任何隐式移动构造函数或移动赋值,而是因为生成了以下形式的复制构造函数;
X(X const&);
这很高兴绑定到临时文件。
在你的第三个示例中,代码无法编译(与移动无关),因为你有一个复制构造函数X(X&);
,但它的形式不适合绑定到临时对象,它需要是 X(X const&);
。临时对象可以绑定到 const 引用。
我相信编译器是正确的。
此代码:
#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] 确实说:
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if [...] X does not have a user-declared copy assignment operator, [...]
因此,X
将没有隐式默认的移动构造函数。但是,请注意替代方案不是 deleted 移动构造函数。简直就是一个不存在的。如果隐式声明的移动构造函数被删除,那么代码将无法编译。但这里不是这样。
但是它对 copy 构造函数说了什么?
If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
X
既没有移动构造函数也没有移动赋值运算符,因此 X
有一个隐式声明的默认复制构造函数。这种情况 已弃用 ,但仍然是有效代码。很可能在未来的某个标准中它会是良构的,但现在还不是!
结果我们的X
真的是:
struct X{
X(){}
X(const X&) = default;
X& operator=(const X& x);
};
和表达式 X y = f();
复制初始化 y
。它不能移动初始化 y
,因为我们没有移动构造函数,但复制构造函数仍然匹配 X
。