基本クラスとそのサブクラスがあります。
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)は失敗するが、他は機能するのですか?
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つ作成する必要がないため機能します。
(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)は右辺値を返しますが、(2)は右辺値ではなく、unique_ptrでコピーを試行しています。これはunique_ptrでは実行できません。
その時点でunique_ptrを右辺値として扱っているため、moveの使用は機能します。