web-dev-qa-db-ja.com

どの種類のポインターをいつ使用しますか?

さて、最後に私が生計のためにC++を書いたとき、std::auto_ptrはstd libがすべて利用可能で、boost::shared_ptrは大流行でした。提供されている他の種類のスマートポインターを実際に調べたことはありません。 C++ 11は、boostが思いついたタイプの一部を提供するようになりましたが、すべてではありません。

誰かが、どのスマートポインターをいつ使用するかを決定する簡単なアルゴリズムを持っていますか?ダムポインター(T*などの未加工のポインター)およびその他のブーストスマートポインターに関するアドバイスを含めることをお勧めします。 ( this のようなものは素晴らしいでしょう)。

223
sbi

共有所有権:
標準で採用されているshared_ptrweak_ptrは、 Boostの同等物 とほとんど同じです。リソースを共有する必要があり、どれが最後に生き残るかわからない場合に使用します。 weak_ptrを使用すると、サイクルを中断するのではなく、存続期間に影響を与えることなく共有リソースを監視できます。 shared_ptrを使用したサイクルは通常発生しません。2つのリソースが互いに所有することはできません。

Boostには shared_array が追加されていることに注意してください。これはshared_ptr<std::vector<T> const>の適切な代替手段かもしれません。

次に、Boostは intrusive_ptr を提供します。これは、リソースが既に参照カウント管理を提供していて、それをRAII原則に採用したい場合の軽量ソリューションです。これは規格に採用されていません。

一意の所有権:
Boostには scoped_ptr もあり、これはコピーできず、削除者を指定できません。 std::unique_ptrはステロイドではboost::scoped_ptrであり、スマートポインターが必要な場合のデフォルトの選択である必要があります。テンプレート引数で削除機能を指定でき、boost::scoped_ptrとは異なり、movableです。また、コピー可能な型を必要とする操作を使用しない限り、STLコンテナーで完全に使用できます(明らかに)。

Boostの配列バージョンは scoped_array であることに注意してください。これは、deleteing(std::unique_ptr<T[]>rを使用)の代わりにdelete[]ポインターをdefault_delete部分的に特殊化することを要求することで統一された標準です。 std::unique_ptr<T[]>は、operator[]およびoperator*の代わりにoperator->も提供します。

std::auto_ptrはまだ標準になっていますが、廃止予定です§D.10 [depr.auto.ptr]

クラステンプレートauto_ptrは非推奨です。 [注:クラステンプレートunique_ptr(20.7.1)は、より良いソリューションを提供します。 —メモの終了]

所有権なし:
ダムポインター(生のポインター)または非所有参照リソースへの参照を使用し、リソースが存続する参照オブジェクト/スコープを知っている場合。ヌル可能またはリセット可能のいずれかが必要な場合は、参照を優先し、生のポインターを使用します。

リソースへの非所有参照が必要であるが、そのリソースがそれを参照するオブジェクトよりも存続するかどうかわからない場合は、リソースをshared_ptrにパックし、weak_ptrを使用します-親shared_ptrが生きているかどうかをテストできますlock。リソースがまだ存在する場合、null以外のshared_ptrを返します。リソースが停止しているかどうかをテストする場合は、expiredを使用します。この2つは似ているように聞こえますが、expiredはその単一ステートメントの戻り値のみを保証するため、同時実行の面では非常に異なります。一見無害なテストのような

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

潜在的な競合状態です。

177
Xeo

どのスマートポインターを使用するかを決定することは、所有権の問題です。リソース管理に関しては、オブジェクトA ownsオブジェクトBがオブジェクトBの存続期間を制御している場合、たとえば、メンバー変数の存続期間は関連付けられているため、メンバー変数はそれぞれのオブジェクトによって所有されます。オブジェクトの寿命まで。オブジェクトの所有方法に基づいて、スマートポインターを選択します。

ソフトウェアシステムの所有権は、ソフトウェアの外部で考えると所有権とは別のものであることに注意してください。たとえば、人は自分の家を「所有」するかもしれませんが、それは必ずしもPersonオブジェクトがHouseオブジェクトの存続期間を制御することを意味するわけではありません。これらの現実世界の概念をソフトウェアの概念と融合させることは、自分自身を穴にプログラムする確実な方法です。


オブジェクトの唯一の所有権がある場合は、std::unique_ptr<T>を使用します。

オブジェクトの所有権を共有している場合...
-所有権にサイクルがない場合は、std::shared_ptr<T>を使用します。
-サイクルがある場合、「方向」を定義し、一方の方向でstd::shared_ptr<T>を使用し、もう一方の方向でstd::weak_ptr<T>を使用します。

オブジェクトがあなたを所有しているが、所有者がいない可能性がある場合は、通常のポインターT*を使用します(例:親ポインター)。

オブジェクトがあなたを所有している場合(または存在が保証されている場合)、参照T&を使用します。


警告:スマートポインターのコストに注意してください。メモリまたはパフォーマンスが制限された環境では、メモリを管理するためのより手動のスキームで通常のポインタを使用するだけで有益な場合があります。

費用:

  • カスタムの削除プログラムがある場合(たとえば、割り当てプールを使用する場合)、ポインターごとにオーバーヘッドが発生しますが、手動で削除することで簡単に回避できます。
  • std::shared_ptrには、コピー時の参照カウント増分のオーバーヘッドに加えて、破棄時のデクリメントとそれに続く保持オブジェクトの削除を伴う0カウントチェックのオーバーヘッドがあります。実装によっては、コードが肥大化し、パフォーマンスの問題が発生する可能性があります。
  • コンパイル時間。すべてのテンプレートと同様に、スマートポインターはコンパイル時間に悪影響を及ぼします。

例:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

バイナリツリーは親を所有しませんが、ツリーの存在は親(またはルートの場合はnullptr)の存在を意味するため、通常のポインターを使用します。 (値のセマンティクスを持つ)バイナリツリーは、その子の唯一の所有権を持っているので、それらはstd::unique_ptrです。

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

ここでは、リストノードは次のリストと前のリストを所有しているため、方向を定義し、nextにshared_ptrを、prevにweak_ptrを使用してサイクルを中断します。

127
Peter Alexander

参照カウントが必要な場合を除き、常にunique_ptr<T>を使用します。その場合、shared_ptr<T>を使用します(非常にまれなケースでは、参照サイクルを防ぐためにweak_ptr<T>を使用します)。ほとんどの場合、譲渡可能な一意の所有権は問題ありません。

生のポインター:共変の戻り値が必要な場合にのみ有効です。それ以外の場合、それらは非常に便利ではありません。

配列ポインター:unique_ptrには、結果に対してT[]を自動的に呼び出すdelete[]の特殊化があるため、たとえばunique_ptr<int[]> p(new int[42]);を安全に実行できます。 shared_ptrカスタム削除機能はまだ必要ですが、特殊な共有または一意の配列ポインターは必要ありません。もちろん、そのようなものは通常、とにかくstd::vectorに置き換えるのが最適です。残念ながらshared_ptrは配列アクセス関数を提供しないため、get()を手動で呼び出す必要がありますが、unique_ptr<T[]>operator[]operator*ではなくoperator->を提供します。いずれにせよ、自分で境界チェックする必要があります。これにより、shared_ptrのユーザーフレンドリーがやや低下しますが、おそらく一般的な利点があり、Boost依存関係がないため、unique_ptrshared_ptrが再び勝者になります。

スコープポインター:unique_ptrと同様に、auto_ptrによって無関係になりました。

本当にそれ以上何もありません。移動セマンティクスのないC++ 03では、この状況は非常に複雑でしたが、C++ 11ではアドバイスは非常に簡単です。

intrusive_ptrinterprocess_ptrなど、他のスマートポインターの使用法はまだあります。ただし、それらはvery nicheであり、一般的なケースでは完全に不要です。

19
Puppy

unique_ptrを使用する場合のケース:

  • 工場メソッド
  • ポインターであるメンバー(ピルを含む)
  • Stlコンテナーにポインターを格納する(移動を避けるため)
  • 大きなローカルダイナミックオブジェクトの使用

shared_ptrを使用する場合のケース:

  • スレッド間でオブジェクトを共有する
  • 一般的なオブジェクトの共有

weak_ptrを使用する場合のケース:

  • 一般的な参照として機能する大きなマップ(すべての開いているソケットのマップなど)

自由に編集して追加してください

7
Lalaland