web-dev-qa-db-ja.com

shared_ptr <>はweak_ptr <>に対するものであり、unique_ptr <>は...何に対するものですか?

C++ 11では、shared_ptr<>を使用してオブジェクトまたは変数との所有権関係を確立し、weak_ptr<>を使用して、所有されていない方法でそのオブジェクトを安全に参照できます。

unique_ptr<>を使用して、オブジェクトまたは変数との所有権関係を確立することもできます。しかし、所有していない他のオブジェクトがそのオブジェクトも参照したい場合はどうなりますか?この場合、weak_ptr<>は役に立ちません。生のポインタは便利ですが、さまざまな欠点があります(たとえば、 自動的にnullptrに初期化される の可能性がありますが、これはstd::*_ptr<>タイプと一致しない手法によって実現されます)。

weak_ptr<>を介して所有されるオブジェクトへの非所有参照の場合、unique_ptr<>に相当するものは何ですか?

ここに私が取り組んでいるゲームの何かに似ている明確な例があります。

class World
{
public:

    Trebuchet* trebuchet() const { return m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet* theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Duh. Oops. Dumb error. Nice if the compiler helped prevent this.
    }

private:

    Trebuchet* m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}

ここでは、生のポインタを使用して、他の場所でunique_ptr<>を介して所有されているオブジェクトとの非所有関係を維持しています。しかし、生は私たちにできる最善のものですか?

希望は、次のようなタイプのポインタです。

  • 他の最新のポインタタイプのように見えます。例えば。 std::raw_ptr<T>
  • 生のポインターを置換して、全体で最新のポインター型を使用するコードベースが_ptr<の検索を介して(おおよそ)allポインターを見つけられるようにします。
  • Nullptrに自動初期化します。

したがって:

int* p;                  // Unknown value.
std::raw_ptr< int > p;   // null.

この型は現在C++にすでに存在しますか、それとも将来のために提案されていますか、それとも広く利用可能な別の実装ですか?ブースト?

54
OldPeculier

標準のポインタ型が、std::unique_ptr<>に対する所有権がなく、安価で、適切に動作するカウンターポイントとして機能するという真のニーズがあります。そのようなポインタはまだ標準化されていませんが、 標準が提案されています であり、C++標準委員会によって議論されています。 "World's Dumbest Smart Pointer"、別名std::exempt_ptr<>は、他の最新のC++ポインタークラスの一般的なセマンティクスを持ちますが、(shared_ptrおよびunique_ptr doとして)指し示されたオブジェクトを所有すること、またはその削除に正しく応答することのいずれに対しても責任を負いません。オブジェクト(weak_ptrと同様)。

この機能が最終的に委員会によって承認されると仮定すると、この質問で強調されたニーズを完全に満たします。それが委員会によって承認されなくても、上記のリンクされた文書は必要性を完全に表し、完全な解決策を説明します。

21
OldPeculier

shared_ptrの「通知」動作には、参照カウント制御ブロックの参照カウントが必要です。 shared_ptrの参照カウント制御ブロックは、これに個別の参照カウントを使用します。 weak_ptrインスタンスはこのブロックへの参照を維持し、weak_ptrs自体が参照カウント制御ブロックがdeleteedになるのを防ぎます。指し示されたオブジェクトは、強いカウントがゼロになるとデストラクタが呼び出され(そのオブジェクトが格納されていたメモリのdeleteionが発生する場合と発生しない場合があります)、弱い参照カウントがゼロになった場合にのみ制御ブロックがdeleteedになります。

unique_ptrの信条は、プレーンポインターのオーバーヘッドがゼロであることです。 (weak_ptr- ishセマンティクスをサポートするために)参照カウント制御ブロックを割り当てて維持すると、その原則が破られます。その記述の動作が必要な場合は、オブジェクトへの他の参照が所有していない場合でも、共有セマンティクスが本当に必要です。その場合、まだ共有が行われています-オブジェクトが破棄されたかどうかの状態の共有。

所有していない一般的な参照が必要で、通知は必要ない場合は、unique_ptr内のアイテムへのプレーンポインターまたはプレーンリファレンスを使用します。


編集:

あなたの例の場合、VictimTrebuchet&ではなくTrebuchet*を要求するはずです。次に、問題のオブジェクトを誰が所有しているかが明確になります。

class World
{
public:

    Trebuchet& trebuchet() const { return *m_trebuchet.get(); }

private:
    std::unique_ptr< Trebuchet > m_trebuchet;
};

class Victim
{
public:
    Victim( Trebuchet& theTrebuchet ) : m_trebuchet( theTrebuchet ) {}

    ~Victim()
    {
        delete m_trebuchet;     // Compiler error. :)
    }

private:

    Trebuchet& m_trebuchet;    // Non-owning.
};

shared_ptr< Victim > createVictim( World& world )
{
    return make_shared< Victim >( world.trebuchet() );
}
38
Billy ONeal

unique_ptrの非借用アナログは、単純なCポインターです。違い-Cポインタは、ポイントされたデータがまだアクセス可能かどうかを認識しません。一方、weak_ptrはそうです。しかし、rawポインターを、追加のオーバーヘッドなしでデータの有効性を知っているポインターに置き換えることは不可能です(そしてweak_ptrにはそのオーバーヘッドがあります)。これは、Cスタイルのポインターがunique_ptrの非借用アナログとして取得できる速度の点で最高であることを意味します。

7
sasha.sochka

一意に所有されているオブジェクトへの「弱い」ポインタを無料で取得することはできませんが、この概念は有用であり、いくつかのシステムで使用されています。実装については ChromiumのWeakPtr および QTのQPointer を参照してください。

ChromiumのWeakPtrは、weak-referenceableオブジェクト内にshared_ptrを格納し、オブジェクトが破棄されたときに無効としてマークすることにより、侵入的に実装されます。次に、WeakPtrはそのControlBlockを参照し、生のポインターを渡す前にそれが有効かどうかを確認します。 QTのQPointerも同様に実装されていると思います。所有権は共有されないため、元のオブジェクトは確定的に破棄されます。

ただし、これはWeakUniquePtrの逆参照がスレッドセーフではないことを意味します:

スレッド1:

unique_ptr<MyObject> obj(new MyObject);
thread2.send(obj->AsWeakPtr());
...
obj.reset();  // A

スレッド2:

void receive(WeakUniquePtr<MyObject> weak_obj) {
  if (MyObject* obj = weak_obj.get()) {
    // B
    obj->use();
  }
}

A行がB行と同時に実行される場合、スレッド2はぶら下がりポインタを使用して終了します。 std::weak_ptrスレッド2に使用させる前に、オブジェクトへの共有所有参照を原子的に取得する によってこの問題を防止しますが、これはオブジェクトが一意に所有されているという上記の仮定に違反します。つまり、WeakUniquePtrの使用は、実際のオブジェクトの破棄と同期する必要があり、そのための最も簡単な方法は、同じスレッドのメッセージループで実行することを要求することです。 (WeakUniquePtrを使用する前に、スレッド間で前後にコピーしても安全です。)

標準のライブラリタイプを使用してこれを実装するためにstd::unique_ptrでカスタム削除機能を使用することを想像できますが、それは読者のための演習として残しておきます。

6
Jeffrey Yasskin
boost::optional<Trebuchet&>

Billy ONealが彼の回答で指摘したように、ポインタの代わりにTrebuchet&を渡したいと思うでしょう。参照の問題は、nullptrを渡すことができないことです。boost::optionalは、nullptrと同等の機能を提供する方法を提供します。 boost :: optionalの詳細はこちら: http://www.boost.org/doc/libs/1_54_0/libs/optional/doc/html/boost_optional/detailed_semantics.html

この質問も参照してください: boost :: optional <T&> vs T *

注:std::optional<T>はC++ 14への導入が予定されていますが、std::optional<T&>は現在のC++ 14ドラフトにはない個別の提案です。詳細はこちら: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html

3
ChetS

otn::raw::weak(from C++ Object Token Library )は、所有しておらず、安価で、std::unique_ptrに対する適切に動作するカウンターポイントです。また、ライブラリにはotn::safe::uniqueがあります。これは、所有者ではないotn::safe::weakにオブジェクトの削除について「通知」できる一意の所有者です。

#include <otn/all.hpp>
#include <iostream>

int main()
{
    using namespace std;
    using namespace otn;

    raw::weak_optional<int> raw_weak;
    if (!raw_weak)
        cout << "raw_weak is empty" << endl;

    cout << "--- create object in std_unique..." << endl;
    auto std_unique = std::make_unique<int>(42);
    raw_weak = std_unique;
    if (std_unique)
        cout << "std_unique is not empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty" << endl;

    cout << "--- move std_unique to safe_unique..." << endl;
    safe::unique_optional<int> safe_unique = std::move(std_unique);

    if (!std_unique)
        cout << "std_unique is empty" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is observs safe_unique" << endl;

    safe::weak_optional<int> safe_weak = safe_unique;
    if (safe_unique)
        cout << "safe_unique is not empty" << endl;
    if (!safe_weak.expired())
        cout << "safe_weak is not expired" << endl;

    cout << "--- destroy object in safe_unique..." << endl;
    utilize(std::move(safe_unique));
    if (!safe_unique)
        cout << "safe_unique is empty" << endl;
    if (safe_weak.expired())
        cout << "safe_weak is expired, it is not dangling" << endl;
    if (raw_weak)
        cout << "raw_weak is not empty, it is dangling!!!" << endl;
}

出力:

raw_weak is empty
--- create object in std_unique...
std_unique is not empty
raw_weak is not empty
--- move std_unique to safe_unique...
std_unique is empty
raw_weak is not empty, it is observs safe_unique
safe_unique is not empty
safe_weak is not expired
--- destroy object in safe_unique...
safe_unique is empty
safe_weak is expired, it is not dangling
raw_weak is not empty, it is dangling!!!
1
ViTech

Shared_ptr、weak_ptr、unique_ptrを備えた新しいC++の世界では、生のポインターまたは参照を使用して、トレビュシェットなどのオブジェクトへの長期間有効な参照を格納しないでください。代わりに、Worldはトレビュシェットへのshared_ptrを持ち、Victimは、ワールドが離れた場合にトレビュシェットが犠牲者に固執するかどうかに応じて、shared_ptrまたはweak_ptrを格納する必要があります。 weak_ptrを使用すると、ポインターがまだ有効である(つまり、ワールドがまだ存在する)かどうかを確認できます。これを生のポインターまたは参照で行う方法はありません。

Unique_ptrを使用するときは、Worldインスタンスのみがトレビュシェを所有することを宣言しています。 Worldクラスのクライアントは、「get」メソッドを呼び出すことにより、Worldオブジェクトの投石機を使用できますが、それを使用したときに、メソッドによって返される参照またはポインターを保持してはなりません。代わりに、「get」メソッドを呼び出して、トレビュシェットを使いたいときに毎回「借りる」必要があります。

上記は、shared_ptrのオーバーヘッドを回避するために、将来の使用のために参照または生のポインタを格納する必要がある場合があると述べています。しかし、それらのインスタンスは数が少なく、遠く離れているため、投石器を所有するWorldオブジェクトが消えた後は、ポインターまたは参照を使用しないことを完全に確認する必要があります。

1
Brett Hall

生のポインタまたは参照を受け取る関数は、関数が戻った後、そのポインタのコピーを保持しないことを暗黙的に約束します。代わりに、呼び出し元は、呼び出し先が戻るまでポインタが有効であること(またはnullptr)を約束します。

ポインタを保持したい場合は、それを共有します(shared_ptrを使用する必要があります)。 unique_ptrは、ポインタのsingleコピーを管理します。生のポインタ(または参照)を使用して、そのオブジェクトに関連する呼び出し関数を参照します。

これはshared_ptrオブジェクトでも同じです。 weak_ptrが関係するのは、関連する機能よりも長く先のとがったオブジェクトへの参照を追加したい場合のみです。 weak_ptrの主な目的は、2つのオブジェクトが互いに参照を保持している(したがって、解放されない)参照サイクルを中断することです。

ただし、shared_ptrまたはweak_ptrを取得すると、そのパラメーターを取得する関数が他のオブジェクトを(オプションで)変更して、関数の呼び出しよりも長く有効なポイントされたオブジェクトへの参照を保持することを意味します。ほとんどの場合、shared_ptrまたはweak_ptrの場合でも、生のポインタ(nullptrが有効な値の場合)またはref(値が保証されている場合)を使用します。

0
Paul de Vrieze