我很难理解为什么以下副本初始化无法编译:
#include <memory>
struct base{};
struct derived : base{};
struct test
{
test(std::unique_ptr<base>){}
};
int main()
{
auto pd = std::make_unique<derived>();
//test t(std::move(pd)); // this works;
test t = std::move(pd); // this doesn't
}
unique_ptr<derived>
可以移动到unique_ptr<base>
,那么为什么第二个语句有效,而最后一个语句无效呢?执行副本初始化时是否不考虑非显式构造函数?
gcc-8.2.0的错误为:
conversion from 'std::remove_reference<std::unique_ptr<derived, std::default_delete<derived> >&>::type'
{aka 'std::unique_ptr<derived, std::default_delete<derived> >'} to non-scalar type 'test' requested
从clang-7.0开始是
candidate constructor not viable: no known conversion from 'unique_ptr<derived, default_delete<derived>>'
to 'unique_ptr<base, default_delete<base>>' for 1st argument
此处提供实时代码。
std::unique_ptr<base>
与std::unique_ptr<derived>
的类型不同。当你做
test t(std::move(pd));
调用std::unique_ptr<base>
的转换构造函数将pd
转换为std::unique_ptr<base>
。这很好,因为您可以进行单个用户定义的转换。
在中
test t = std::move(pd);
您正在进行复制初始化,因此需要将pd
转换为test
。这需要2个用户定义的转换,但你不能这样做。首先必须将pd
转换为std::unique_ptr<base>
,然后需要将其转换为test
。这不是很直观,但当你有时
type name = something;
无论CCD_ 13是什么,都只需要是来自源类型的单个用户定义的转换。在你的情况下,这意味着你需要
test t = test{std::move(pd)};
其仅使用像第一种情况那样定义的单个隐式用户。
让我们删除std::unique_ptr
并在一般情况下查看。由于std::unique_ptr<base>
与std::unique_ptr<derived>
不是同一类型,因此我们本质上有
struct bar {};
struct foo
{
foo(bar) {}
};
struct test
{
test(foo){}
};
int main()
{
test t = bar{};
}
我们得到了同样的错误,因为我们需要从bar -> foo -> test
开始,而这有一个用户定义的转换太多了。
如果目标类型是(可能是cv限定的(类类型:
[…]
否则,如果初始化是直接初始化,或者如果是源的cv不合格版本的复制初始化类型与目的地,构造函数被考虑在内。适用的构造函数枚举([over.match.ctor](,并通过过载解决方案。如此选择的构造函数被调用初始化对象,使用初始值设定项表达式或表达式列表作为其参数。如果没有构造函数应用,或者过载解决方案不明确,初始化格式不正确。
否则(即,对于剩余的副本初始化情况(,可以从源转换的用户定义转换序列type转换为目标类型或(使用转换函数时(到其派生类,如中所述枚举[over.match.copy],通过过载选择最佳决议如果转换无法完成或不明确初始化格式不正确。使用调用所选函数初始值设定项表达式作为其参数;如果函数是构造函数,该调用是的cv不合格版本的prvalue目标类型,其结果对象由构造函数。根据上面的规则,作为复制初始化。
在直接初始化的情况下,我们输入第一个带引号的项目符号。正如那里详细介绍的那样,构造函数是直接考虑和枚举的。因此,所需的隐式转换序列仅用于将unique_ptr<derived>
转换为unique_ptr<base>
作为构造函数参数。
在复制初始化的情况下,我们不再直接考虑构造函数,而是尝试查看哪种隐式转换序列是可能的。唯一可用的是unique_ptr<derived>
到unique_ptr<base>
到test
。由于隐式转换序列只能包含一个用户定义的转换,因此这是不允许的。因此,初始化是不正确的。
可以说,使用直接初始化在某种程度上"绕过"了一个隐式转换。
非常确定编译器只允许考虑单个隐式转换。在第一种情况下,只需要从std::unique_ptr<derived>&&
到std::unique_ptr<base>&&
的转换,在第二种情况下还需要将基指针转换为test
(以便默认的移动构造函数工作(。因此,例如,将派生指针转换为基:std::unique_ptr<base> bd = std::move(pd)
,然后移动分配它也可以。