std::unique_ptr 返回多态对象的正确方法 class

The correct way of returning std::unique_ptr to an object of polymorphic class

假设我有以下 类 的层次结构:

struct Base 
{
};

struct Derived : public Base 
{ 
    void DoStuffSpecificToDerivedClass() 
    {
    } 
};

以及以下工厂方法:

std::unique_ptr<Base> factoryMethod()
{
    auto derived = std::make_unique<Derived>();
    derived->DoStuffSpecificToDerivedClass();
    return derived; // does not compile
}

问题是,return 语句无法编译,因为 std::unique_ptr 没有支持协变的复制构造函数(这是有道理的,因为它没有任何复制构造函数),它只有一个支持协方差的移动构造函数。

解决这个问题的最佳方法是什么?我可以想到两种方法:

return std::move(derived); // this compiles
return std::unique_ptr<Base>(derived.release()); // and this compiles too

编辑 1: 我使用 Visual C++ 2013 作为我的编译器。 return derived 的原始错误消息如下所示:

Error   1   error C2664: 'std::unique_ptr<Base,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : cannot convert argument 1 from 'std::unique_ptr<Derived,std::default_delete<Derived>>' to 'std::unique_ptr<Derived,std::default_delete<Derived>> &&'

编辑 2: 这是一个从标准 VS 2013 模板新建的控制台应用程序。我没有调整任何编译器设置。编译器命令行如下所示:

调试:

/Yu"stdafx.h" /GS /analyze- /W3 /Zc:wchar_t /ZI /Gm /Od /sdl /Fd"Debug\vc120.pdb" /fp:precise /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /Oy- /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\CppApplication1.pch" 

发布:

/Yu"stdafx.h" /GS /GL /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /Fa"Release\" /EHsc /nologo /Fo"Release\" /Fp"Release\CppApplication1.pch" 

你可以这样做:

return std::move(derived);

这样就告诉编译器不需要copy,满足了unique_ptr的要求。如果类型完全匹配,则不需要显式指定 move,但在这种情况下需要指定。

如问题中所述,问题是,return 语句无法编译,std::unique_ptr 没有支持协方差的复制构造函数,它只有一个支持协方差的移动构造函数, 但是,编译器仍然没有从 std::unique_ptr<Derived>.

移动

这是因为从函数 returned 从对象移动的条件与复制省略的标准密切相关,这严格要求被 returned 的对象类型需要与函数的 return 类型相同。

[class.copy]/32:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

所以,我更喜欢,

return std::move(derived);

但是,DR-9R5 so that the return value will be treated as an rvalue even when the types are not the same, gcc-5 implemented the rule and you don't need to change your code for gcc-5 as shown here 中有规则更改。