尝试编译以下代码:
struct Foo
{
explicit Foo ( void ) { }
explicit Foo ( Foo&& rhs ) { }
};
Foo bar ( void )
{
return Foo();
}
收到以下错误:
调用隐式删除的'Foo'
复制构造函数
嗯,很明显,拷贝代码被隐式删除了。
问题1:为什么编译器需要Foo
的copy-ctor?我希望bar
的返回值可以使用move-ctor从右值Foo()
构建。
然后我将move-ctor重新声明为隐式,并且所有内容都成功编译。
问题2:当我将move-ctor重新声明为隐式时,为什么编译器不再需要copy-ctor?
问题3: explicit
关键字在复制和移动ctors的上下文中意味着什么,因为它肯定意味着与常规ctors的上下文不同。
答案 0 :(得分:10)
这是因为返回一个值被视为implicit conversion。
引用C ++ 11标准:
6.6.3退货声明
2 [...]
表达式为非void类型的return语句只能在返回值的函数中使用;表达式的值返回给函数的调用者。 表达式的值被隐式转换为它出现的函数的返回类型。 return语句可能涉及构造和复制或移动临时对象(12.2)。 [...]
从返回表达式到临时对象的转换以保存返回值是隐式的。因此,这会导致错误
Foo f = Foo(); // Copy initialization, which means implicit conversion
以代码为例,也会触发类似的代码。
问题1:为什么编译器需要Foo的copy-ctor?我期望使用move-ctor从rvalue Foo()构造bar的返回值。
因为上述原因,Foo(Foo&&)
不是可行的超载。规则规定,无论何时移动构造函数都不能使用,编译器应该考虑复制构造函数,在您的情况下,由于存在用户定义的移动构造函数,它会被隐式删除。
问题2:当我将move-ctor重新声明为隐式时,为什么编译器不再需要copy-ctor?
这是因为您的移动构造函数现在可以使用了。因此,编译器可以立即使用它,甚至不用考虑是否存在复制构造函数。
问题3:显式关键字在复制和移动ctors的上下文中意味着什么,因为它肯定意味着与常规ctors的上下文不同。
恕我直言,这没有任何意义,只会导致问题,就像你的情况一样。
答案 1 :(得分:6)
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 :因为它使用移动构造函数隐式地从Foo&&
转换为Foo
。
Q3 :它与普通转换构造函数的含义相同。
答案 2 :(得分:2)
这与过载解析在C ++中的工作原理有关。
重载解析的第一步是形成一组候选函数。第二步是将候选函数集缩小为一组可行函数。第三步是选择唯一的最佳可行功能(如果有)。如果删除最佳可行功能,则该程序格式不正确。
因为移动构造函数被声明为explicit
,所以不是候选函数,用于将Foo()
隐式转换为函数的返回类型。唯一的候选函数是Foo::Foo(const Foo&)
,隐式声明的复制构造函数。 这是候选功能,即使它被声明为已删除。它是通过重载分辨率选择的,是唯一可行的功能;然后在尝试调用已删除的函数时出错。
如果你没有声明移动构造函数explicit
,那么两者移动构造函数和隐式声明的复制构造函数都是候选函数。在这种情况下,两者都是可行的。但是,移动构造函数会获得重载解析,因为rvalues更喜欢通过const左值引用绑定到rvalue引用。因此,移动构造函数是最可行的函数。在这种情况下,复制构造函数被删除并不重要,因为它会在重载决策中丢失。
TL; DR:
回答1 :因为移动构造函数不是转换的候选函数。
回答2 :因为移动构造函数是候选函数,并且赢得重载解析。
回答3 :不,这意味着同样的事情。