将std::unique_ptr<Derived>转换为std::unique_ptr<Base>

47

使用C++11,假设我有处理基类和派生类的工厂函数:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
    auto b = unique_ptr<B>( new B() );
    return b; // Ok!
}

unique_ptr<B> MakeD()
{
    auto d = unique_ptr<D>( new D() );
    return d; // Doh!
}
在上面的最后一行中,我需要使用move(d)才能使其工作,否则会出现"Error: invalid conversion from std::unique_ptr<D> to std::unique_ptr<D>&&." 我的直觉认为,在这种情况下,编译器应该知道它可以隐式地将d转换为rvalue并将其移动到基指针中,但它没有这样做。

这是我的编译器(gcc 4.8.1和VS2012)不符合标准吗?还是unique_ptr的预期设计?标准中的缺陷?


更新:C++14修复了这个问题。新的编译器,如GCC 9,即使使用-std=c++11,也接受原始代码


只有在类型相同时才会存在隐式移动。 - Simple
@Simple:你有规范参考来支持这个说法吗?(如果有的话,那将是正确的答案) - Jan Hudec
3
@JanHudec 12.8/31和12.8/32。隐式转换是指编译器未能省略允许省略的情况,当类型不同时,不允许省略。 - Simple
2个回答

35
编译器的行为是正确的。只有当类型相同时才会有隐式移动,因为隐式移动是指在实际允许的情况下编译器无法执行复制省略时发生的(参见12.8/31和12.8/32)。
12.8/31(复制省略):
在具有类返回类型的函数中的return语句中,当表达式是非易失性自动对象的名称时(不包括函数或catch字句参数),并且该对象与函数返回类型具有相同的cv-unqualified类型...
12.8/32(隐式移动):
当满足复制操作的省略条件时,[...],首先执行重载分辨率,以选择用于复制的构造函数,就好像对象是由rvalue指定的一样。

24
+1,但请注意这已经在C++14中进行了更改,DR 1579在上次会议上被批准。 - Jonathan Wakely
谢谢,@Simple:那绝对回答了问题。还有谢谢,@JonathanWakely:这证实了我的直觉,它应该可以在没有move()的情况下工作。 - metal

-3

通过在return语句中添加std::move调用,这段代码在Visual Studio 2013上可以正常工作:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
  auto b = unique_ptr<B>( new B() );
  return std::move( b );  // *** std::move() added here! ***
}

unique_ptr<B> MakeD()
{
  auto d = unique_ptr<D>( new D() );
  return std::move( d );  // *** std::move() added here! ***
}

以下方法也可行

unique_ptr<B> MakeB()
{
  return unique_ptr<B>( new B() );  // *** Returning a temporary! ***
}

unique_ptr<B> MakeD()
{
  return unique_ptr<D>( new D() );  // *** Returning a temporary! ***
}

当然,如果你最终要返回std::unique_ptr<B>,为什么不一开始就把D实例放进一个std::unique_ptr<B>中呢?这样就完全不需要进行转换了!


2
好的,这甚至在问题中都已经说明了。真正的问题是为什么在这里需要使用std::move() - Angew is no longer proud of SO
抱歉,完全忽略了那个。嗯,不过没关系。(但是嘿,它在VS2013中仍然可以工作!万岁!:-P) - aldo
@pepper_chico 但是被接受的答案说它是符合规范的! - aldo
@aldo 接受的答案表示,原帖中编译器错误报告是正确的行为,这意味着原帖中的代码不符合标准。而你所说的似乎是允许返回而没有错误,这目前是不正确的。不清楚你是否在说VS2013正在编译原帖中的代码,看起来你很困惑!因为它确实是这样的。 - oblitum
@pepper_chico 我编辑了我的答案,加入了代码注释,强调我已经添加std::move调用(正如OP建议的那样),这使得代码符合标准(惊喜!)并且可以编译而没有错误。 - aldo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接