web-dev-qa-db-ja.com

unique_ptrのstatic_pointer_castの代替

static_pointer_cast with unique_ptrは、含まれるデータの共有所有権につながります。
他の用語では、私がやりたいことは:

unique_ptr<Base> foo = fooFactory();
// do something for a while
unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo);

とにかく2つのunique_ptr同時に存在することはないため、単に禁止されています。
そうです、まったく理にかなっています。だから、static_unique_pointer_cast 確かに。

これまで、これらの基本クラスへのポインターを保存したいが、いくつかの派生クラスにキャストする必要がある場合(例として、型消去を含むシナリオを想像してください)、shared_ptrs私が上で言及したことのため。

とにかく、shared_ptrsそのような問題の場合、またはそれらが本当にその場合の最良の解決策である場合。

21
skypjack

生のポインター

あなたの問題の解決策は、生の(非所有)ポインターを取得してキャストすることです-そして、生のポインターをスコープから外し、残りの_unique_ptr<Base>_が所有オブジェクトの存続期間を制御できるようにします。

このような:

_unique_ptr<Base> foo = fooFactory();

{
    Base* tempBase = foo.get();
    Derived* tempDerived = static_cast<Derived*>(tempBase);
} //tempBase and tempDerived go out of scope here, but foo remains -> no need to delete
_

Unique_pointer_cast

もう1つのオプションは、_unique_ptr_のrelease()関数を使用して、別のunique_ptrにラップすることです。

このような

_template<typename TO, typename FROM>
unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){
    return unique_ptr<TO>{static_cast<TO*>(old.release())};
    //conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO>
}

unique_ptr<Base> foo = fooFactory();

unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo));
_

これは古いポインターfooを無効にすることを覚えておいてください

生のポインタからの参照

答えを完全にするために、このソリューションは実際にはコメント内のOPによる生のポインタの小さな修正として提案されました。

生のポインタを使用するのと同様に、生のポインタをキャストしてから、逆参照することでそれらから参照を作成できます。この場合、作成された参照のライフタイムがunique_ptrのライフタイムを超えないことを保証することが重要です。

サンプル:

_unique_ptr<Base> foo = fooFactory();
Derived& bar = *(static_cast<Derived*>(foo.get()));
//do not use bar after foo goes out of scope
_
32
Anedar

Static_pointer_castとunique_ptrを使用すると、含まれているデータの所有権が共有されることを理解しています。

誤って定義した場合のみ。明らかな解決策は、所有権を譲渡することで、ソースオブジェクトが空になります。

所有権を譲渡したくない場合は、生のポインタを使用します。

または、2人の所有者が必要な場合は、_shared_ptr_を使用します。

あなたの質問の一部は実際のキャスト操作に関するものであり、一部はポインターの明確な所有権ポリシーの欠如のようです。複数の所有者が必要な場合、両方が同じタイプを使用しているか、1つが別のタイプにキャストされているかに関係なく、_unique_ptr_を使用しないでください。

とにかく、同時に存在してはならない2つのunique_ptrを使用してそれを行うと、単に禁止されます。
そうです、まったく理にかなっています。それが、static_unique_pointer_castのようなものが実際に存在しない理由です。

いいえ、それが存在しない理由ではありません。必要な場合(独自の所有権のセマンティクスを正しければ)自分で書くのは簡単なので、存在しません。 release()キャストでポインターを取り出し、別の_unique_ptr_に入れてください。シンプルで安全。

_shared_ptr_の場合はそうではありません。「明白な」ソリューションは正しいことをしません。

_shared_ptr<Derived> p2(static_cast<Derived*>(p1.get());
_

それは、同じポインターを所有する2つの異なる_shared_ptr_オブジェクトを作成しますが、所有権を共有しません(つまり、両方ともそれを削除しようとして、未定義の動作を引き起こします)。

_shared_ptr_が最初に標準化されたとき、それを行う安全な方法はなかったため、_static_pointer_cast_および関連するキャスト関数が定義されました。彼らは_shared_ptr_簿記情報の実装の詳細にアクセスする必要がありました。

ただし、C++ 11標準化プロセス中に_shared_ptr_は、「エイリアスコンストラクター」の追加により拡張され、これによりキャストを簡単かつ安全に行うことができます。

_shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get());
_

この機能が常に_shared_ptr_の一部であった場合、おそらく_static_pointer_cast_が定義されていなかった可能性があります。

7
Jonathan Wakely

与えられた_std::unique_ptr< U >_のrelease()メンバーメソッドを呼び出す Anedar の以前の回答に何かを追加したいと思います。 _dynamic_pointer_cast_を_static_pointer_cast_に変換するために_std::unique_ptr< U >_も(_std::unique_ptr< T >_に加えて)実装したい場合は、一意のポインターによって保護されているリソースが解放されるようにする必要があります。 _dynamic_cast_が失敗する(つまり、nullptrを返す)場合に適切に。そうしないと、メモリリークが発生します。

コード

_#include <iostream>
#include <memory>

template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    U * const stored_ptr = ptr.release();
    T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr);
    if (converted_stored_ptr) {
        std::cout << "Cast did succeeded\n";
        return std::unique_ptr< T >(converted_stored_ptr);
    }
    else {
        std::cout << "Cast did not succeeded\n";
        ptr.reset(stored_ptr);
        return std::unique_ptr< T >();
    }
}

struct A { 
    virtual ~A() = default;
};
struct B : A {
    virtual ~B() { 
        std::cout << "B::~B\n"; 
    }
};
struct C : A {
    virtual ~C() { 
        std::cout << "C::~C\n"; 
    }
};
struct D { 
    virtual ~D() { 
        std::cout << "D::~D\n"; 
    }
};

int main() {

  std::unique_ptr< A > b(new B);
  std::unique_ptr< A > c(new C);
  std::unique_ptr< D > d(new D);

  std::unique_ptr< B > b1 = dynamic_pointer_cast< B, A >(std::move(b));
  std::unique_ptr< B > b2 = dynamic_pointer_cast< B, A >(std::move(c));
  std::unique_ptr< B > b3 = dynamic_pointer_cast< B, D >(std::move(d));
}
_

出力(可能な順序)

_Cast did succeeded
Cast did not succeeded
Cast did not succeeded
B::~B
D::~D
C::~C
_

CおよびDのデストラクタは、以下を使用する場合、呼び出されません。

_template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    return std::unique_ptr< T >(dynamic_cast< T * >(ptr.release()));
}
_
0
Matthias