2016-09-13 7 views
6

私は、基本クラスとそのサブクラスを持っている:std :: make_unique <SubClass>を返すのはどうですか?

class Base { 
    public: 
    virtual void hi() { 
     cout << "hi" << endl; 
    } 
}; 

class Derived : public Base { 
    public: 
    void hi() override { 
     cout << "derived hi" << endl; 
    } 
}; 

は、派生オブジェクトの一意のポインタを作成するヘルパー関数を作成しようとしています。

1)この1作品:

std::unique_ptr<Base> GetDerived() { 
    return std::make_unique<Derived>(); 
} 

2)しかし、この1つはコンパイルに失敗します。

std::unique_ptr<Base> GetDerived2() { 
    auto a = std::make_unique<Derived>(); 
    return a; 
} 

3)のstd ::動きは働く:

std::unique_ptr<Base> GetDerived3() { 
    auto a = std::make_unique<Derived>(); 
    return std::move(a); 
} 

4 )Baseインスタンスを作成すると、両方とも動作します:

std::unique_ptr<Base> GetDerived4() { 
    auto a = std::make_unique<Base>(); 
    return a; 
} 

std::unique_ptr<Base> GetDerived5() { 
    auto a = std::make_unique<Base>(); 
    return std::move(a); 
} 

なぜ(2)が失敗しますが他はうまくいくのですか?

答えて

7

std::unique_ptrは移動できません。 std::unique_ptr<Base>を返すと宣言された関数からreturn std::make_unique<Derived>できる理由は、一方から他方への変換があるからです。

だから1)に相当する:std::make_uniqueから返された値は、戻り値は移動-構成され、右辺値であるので

std::unique_ptr<Base> GetDerived() { 
    return std::unique_ptr<Base>(std::made_unique<Derived>()); 
} 

aが左辺値であるため、戻り値は、コピー構成されなければならない、とstd::unique_ptr非コピー可能である

std::unique_ptr<Base> GetDerived2() { 
    std::unique_ptr<Derived> a = std::make_unique<Derived>(); 
    return std::unique_ptr<Base>(a); 
} 

:に相当する2への

コントラスト)。

3)は、左辺値aを右辺値にキャストし、戻り値を移動構築することができます。

4)と5)は、すでにstd::unique_ptr<Base>があり、返品するために作成する必要がないため、動作します。

0

上記の例では、(1)はrvalueを返しますが、(2)はrvalueではなく、unique_ptrでコピーしようとしています。

あなたはその時点でunique_ptrをrvalueとして扱っているので、moveを使用しています。

1

std::unique_ptr<>にはコピーコンストラクタを有していないが、それは即ち

unique_ptr(unique_ptr&& u);   // move ctor 
template< class U, class E > 
unique_ptr(unique_ptr<U, E>&& u); // move ctor from related unique_ptr 

第コンストラクタは、特定の条件(here参照)を必要とし、関連するポインタの移動コンストラクタを持っています。では、なぜコード2は動作しませんでしたが、4は動作しましたか? 4では、戻り値の型がオブジェクトと同じであるため、コンストラクタは使用しませんでした。オブジェクト自体が返されました。一方、2では戻り値の型が異なり、コンストラクタ呼び出しが必要でしたが、それにはstd::move()が必要です。

1

(2)(2)返された値は(何らかの)rvalueとして扱われました。(2)では、型が一致しなかったため暗黙の移動がブロックされていませんでした。

標準の後の反復では、(2)も暗黙的に移動します。

のポインタを使用してDerivedオブジェクトを削除しようとすると、それらのオブジェクトはすべて呼び出された後すぐに未定義の動作を行います。これを修正するには、Deleter関数を記録します。

template<class T> 
using smart_unique=std::unique_ptr<T, void(*)(void*)>; 

template<class T, class...Args> 
smart_unique<T> make_smart_unique(Args&&... args){ 
    return { 
    new T(std::forward<Args>(args)...), 
    [](void*ptr){ delete static_cast<T*>(ptr); } 
    }; 
} 
template<class T> 
static const smart_unique<T> empty_smart_unique{ nullptr, [](void*){} }; 

これらは、shared_ptrのような多型性を扱うのに十分なスマートなポインタです。

+1

'Base'に仮想デストラクタを与えると、カスタムデリータはもう必要ありません。 –

+0

@RemyLebeau一方で、アプリケーションに応じて、アプリケーションに応じてvtableを持たないvtableを導入すると、プログラマはvtableを持たないより小さなオブジェクトサイズを好むかもしれません。 –

+1

@MM: 'Base'と' Derived'にはすでにvtableがあります仮想 'hi()'メソッドに渡します。多態性(ポインタ/参照のみで動作する)を扱うときはいつでも、ベースクラスは常に仮想デストラクタを持っているべきで、派生したオブジェクトはベースポインタを使って適切に破棄することができます。 –

関連する問題