web-dev-qa-db-ja.com

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;
    } 
};

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

1)これは機能します:

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

2)しかし、これはコンパイルに失敗します:

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

3)std :: move works:

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)は失敗するが、他は機能するのですか?

11
MaxHeap

std::unique_ptrはコピー可能ではなく、移動可能です。 return std::make_unique<Derived>を返すように宣言された関数からstd::unique_ptr<Base>できる理由は、一方から他方への変換があるためです。

したがって、1)は次と同等です。

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

std::make_uniqueから返される値は右辺値であるため、戻り値は移動構築されます。

これを2)と比較してください。これは、次と同等です。

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

aは左辺値であるため、戻り値はコピーで構成する必要があり、std::unique_ptrはコピーできません。

3)左辺値aを右辺値にキャストし、戻り値を移動構築できるため、機能します。

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

11
Miles Budnek

(2)を除くすべての場合で、戻り値は(ある種の)右辺値として扱われました。 (2)では、タイプが一致しなかったため、暗黙の移動がブロックされました。

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

それらはすべて、Derivedへのポインタを介してBaseオブジェクトを削除しようとするため、呼び出された後すぐに未定義の動作に関与します。これを修正するには、削除機能を記録します。

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のようなポリモーフィズムを処理するのに十分スマートな一意のポインタです。

_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
_

2番目のコンストラクターには特定の条件が必要です( ここ を参照)。では、なぜコード2は機能しなかったのに、4は機能したのでしょうか。 4では、戻り値の型がオブジェクトと同じであったため、コンストラクターを使用しませんでした。オブジェクト自体が返されました。一方、2では、戻り値の型が異なり、コンストラクター呼び出しが必要でしたが、それにはstd::move()が必要でした。

1
Walter

上記の例では、(1)は右辺値を返しますが、(2)は右辺値ではなく、unique_ptrでコピーを試行しています。これはunique_ptrでは実行できません。

その時点でunique_ptrを右辺値として扱っているため、moveの使用は機能します。

0
AhiyaHiya