显式移动构造函数

Explicit move constructor

本文关键字:构造函数 移动      更新时间:2023-10-16

尝试编译以下代码:

struct Foo
{
explicit Foo ( void ) { }
explicit Foo ( Foo&& rhs ) { }
};
Foo bar ( void )
{
return Foo();
}

得到以下错误:

调用隐式删除的"Foo"的复制构造函数

很明显,副本ctor被隐式删除了。

问题1:为什么编译器需要Foo的副本ctor?我期望bar的返回值是由具有move ctor的右值Foo()构造的。

然后,我将move-ctor重新声明为隐式,所有内容都成功编译。

问题2:当我将move ctor重新声明为隐式时,为什么编译器不再需要copy ctor?

问题3:explicit关键字在复制和移动运算符的上下文中意味着什么,因为它肯定意味着不同于常规运算符的上下文。

这是因为返回值被视为隐式转换。

引用C++11标准:

6.6.3返回语句

2[…]

带有非void类型表达式的return语句只能在返回值的函数中使用;表达式的值将返回给函数的调用方表达式的值将隐式转换为它所在函数的返回类型返回语句可能涉及临时对象的构建、复制或移动(12.2)。〔…〕

从返回表达式到保存返回值的临时对象的转换是隐式的。因此,正如这将导致错误一样

Foo f = Foo();   // Copy initialization, which means implicit conversion

以代码为例也会引发类似的情况。


问题1:为什么编译器需要Foo的副本ctor?我希望bar的返回值是由带有move ctor的右值Foo()构造的。

因为上述原因,Foo(Foo&&)不是一个可行的过载。规则规定,无论何时不能使用移动构造函数,编译器都应该考虑复制构造函数,在您的情况下,由于存在用户定义的移动构造函数,复制构造函数被隐式删除。

问题2:当我将move ctor重新声明为隐式时,为什么编译器不再需要copy ctor?

这是因为你的move构造函数现在可以使用了。因此,编译器可以立即使用它,甚至不考虑复制构造函数的存在。

问题3:显式关键字在复制和移动运算符的上下文中意味着什么,因为它肯定意味着与常规运算符的上下文不同的东西。

IMHO,这没有意义,只会导致问题,就像你的情况一样。

bar的返回类型为Foo。没有复制构造函数,显式移动构造函数无法工作,因为仍然需要Foo&&Foo之间的隐式转换。从这个意义上讲,explicit Foo(Foo&&)与任何其他explicit转换构造函数没有什么不同。从不同类型转换时仍然会出现此问题。这是使用int:的类似示例

struct Foo
{
explicit Foo(int) {}
};
Foo bar ( void )
{
return 42; // error: could not convert '42' from 'int' to 'Foo'
}

Q1:因为没有其他东西可以使用。

Q2:因为它使用move构造函数从Foo&&隐式转换为Foo

Q3:它与普通转换构造函数的含义相同。

这与C++中重载解析的工作方式有关。

过载解决的第一步是形成一组候选函数。第二步是将候选函数集缩小为可行函数集。第三步是选择唯一的最佳可行函数(如果有的话)。如果删除了最佳可行函数,那么程序就是格式错误的。

因为move构造函数被声明为explicit,所以它不是从Foo()隐式转换为函数返回类型的候选函数。唯一的候选函数是Foo::Foo(const Foo&),它是隐式声明的复制构造函数这是一个候选函数,即使它被声明为已删除。它是通过过载分辨率选择的,是唯一可行的功能;然后在尝试调用已删除的函数时出现错误。

如果您没有声明移动构造函数explicit,那么移动构造函数和隐式声明的复制构造函数都是候选函数。在这种情况下,两者都是可行的。然而,move构造函数赢得了重载解析,因为相对于const左值引用,右值更喜欢绑定到右值引用。因此,move构造函数是最佳可行函数。在这种情况下,复制构造函数被删除并不重要,因为它在重载解析中丢失了。

tl;dr:

答案1:因为move构造函数不是转换的候选函数

答案2:因为move构造函数是一个候选函数,并且赢得了重载解决方案。

答案3:不,意思是一样的。