web-dev-qa-db-ja.com

std :: unique_ptrをポリモーフィッククラスのオブジェクトに返す正しい方法

次のクラスの階層があるとしましょう。

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には共分散をサポートするコピーコンストラクターがないため(コピーコンストラクターがないため意味があります)、移動するだけです。共分散をサポートするコンストラクター。

この問題を解決するための最良の方法は何ですか?私は2つの方法を考えることができます:

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:これは、標準のVS2013テンプレートから新しく作成されたコンソールアプリです。コンパイラの設定は調整していません。コンパイラのコマンドラインは次のようになります。

デバッグ:

/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" 
14
RX_DID_RX

あなたはこれを行うことができます:

return std::move(derived);

このようにして、unique_ptrの要件を満たすコピーは不要であることをコンパイラーに通知します。タイプが完全に一致する場合は、moveを明示的に指定する必要はありませんが、この場合は指定します。

9
John Zwinck

質問で述べたように、問題は、returnステートメントがコンパイルされない、std::unique_ptrに共分散をサポートするコピーコンストラクターがない、共分散をサポートする移動コンストラクターがあるだけですが、コンパイラーはまだ移動しないということですstd::unique_ptr<Derived>から。

これは、関数から返されたオブジェクトから移動するための条件がコピーの省略の基準と密接に関連しているためです。このため、返されるオブジェクトの型は関数の戻り値の型と同じである必要があります。

[class.copy]/32:

ソースオブジェクトが関数パラメーターであり、コピーされるオブジェクトが左辺値で指定されていることを除いて、コピー操作の省略基準が満たされているか、満たされる場合、コピーのコンストラクターを選択するためのオーバーロード解決は次のようになります。最初は、オブジェクトが右辺値で指定されているかのように実行されました。

したがって、私は好みます、

return std::move(derived);

ただし、 DR-9R5 にルールの変更があり、タイプが同じでない場合でも戻り値が右辺値として扱われるようになっています。gcc-5がルールを実装しているため、ルールを実装する必要はありません。図のようにgcc-5のコードを変更します ここ

7
Alper