「Going Native 2012」ストリームを見ているだけで、についての議論に気付きましたstd::shared_ptr
。Bjarneのやや否定的な見方std::shared_ptr
と、オブジェクトの寿命が不確かな場合の「最後の手段」として使用すべきだという彼のコメントを聞いて、私は少し驚いていました(彼によると、そうではないはずです)。
これをもう少し詳しく説明したい人はいますか?どうすればstd::shared_ptr
オブジェクトの寿命を安全な方法で管理し、管理できますか?
「Going Native 2012」ストリームを見ているだけで、についての議論に気付きましたstd::shared_ptr
。Bjarneのやや否定的な見方std::shared_ptr
と、オブジェクトの寿命が不確かな場合の「最後の手段」として使用すべきだという彼のコメントを聞いて、私は少し驚いていました(彼によると、そうではないはずです)。
これをもう少し詳しく説明したい人はいますか?どうすればstd::shared_ptr
オブジェクトの寿命を安全な方法で管理し、管理できますか?
回答:
所有権の共有を回避できる場合、アプリケーションはよりシンプルでわかりやすくなり、メンテナンス中に発生するバグの影響を受けにくくなります。所有権モデルが複雑または不明確であると、簡単に追跡できない共有状態を介して、アプリケーションのさまざまな部分の結合を追跡するのが困難になる傾向があります。
これを考えると、自動ストレージ期間を持つオブジェクトを使用し、「値」サブオブジェクトを持つことが望ましいです。これに失敗unique_ptr
すると、shared_ptr
最後の手段ではないにしても、望ましいツールのリストの下にあるという点で、良い選択肢になるかもしれません。
Bjarneが住んでいる世界は非常に...学問的であり、より良い用語を求めています。オブジェクトが非常に意図的な関係階層を持つようにコードを設計および構造化できる場合、所有権の関係が厳格で不屈になり、コードは一方向(高レベルから低レベルへ)に流れ、オブジェクトは下位のオブジェクトとのみ通信します階層の場合、の必要性はあまりありませんshared_ptr
。誰かがルールを破らなければならないようなまれな機会に使用するものです。しかし、それ以外の場合は、vector
値セマンティクスを使用するsまたはその他のデータ構造、およびunique_ptr
sを単独で割り当てる必要のあるものにsをすべて貼り付けることができます。
それは住むのに素晴らしい世界ですが、それはあなたが常にやることを得るものではありません。そのようにコードを整理できない場合、作成しようとしているシステムの設計が不可能である(または非常に不快な)ことを意味するため、オブジェクトの共有所有権がますます必要になります。
このようなシステムでは、裸のポインタを保持することは...正確には危険ではありませんが、疑問が生じます。すばらしいことは、オブジェクトの寿命について合理的な構文上の保証をshared_ptr
提供することです。壊れますか?もちろん。しかし、人々も物事をすることができます。の基本的なケアと給餌は、所有権を共有しなければならない割り当てられたオブジェクトに適切な生活の質を提供する必要があります。const_cast
shared_ptr
次に、がありますweak_ptr
。これは、がない場合は使用できませんshared_ptr
。システムが厳密に構造化されている場合は、アプリケーションの構造により、指定されたオブジェクトが確実に存続することを保証して、あるオブジェクトへのネイキッドポインターを保存できます。内部値または外部値へのポインターを返す関数を呼び出すことができます(たとえば、Xという名前のオブジェクトを検索します)。適切に構造化されたコードでは、オブジェクトの寿命が自分の寿命を超えることが保証されている場合にのみ、その関数を使用できます。したがって、そのネイキッドポインターをオブジェクトに格納することは問題ありません。
実際のシステムではその剛性を常に達成できるとは限らないため、寿命を合理的に確保する何らかの方法が必要です。場合によっては、完全な所有権は必要ありません。時々、ポインターが悪いか良いかを知る必要があるだけです。どこからだweak_ptr
にしています。私は例があった可能性が使用されてきたunique_ptr
かをboost::scoped_ptr
、私は使用していたshared_ptr
私はので、具体的に誰かに「揮発性」のポインタを与える必要がありました。寿命のあるポインターは不確定であり、ポインターが破壊されたときに照会できました。
世界の状態が不確定なときに生き残るための安全な方法。
viaの代わりに、ポインターを取得する関数呼び出しによってそれを行うことができましたweak_ptr
か?はい、しかしそれはもっと簡単に壊れる可能性があります。ネイキッドポインターを返す関数には、ユーザーがそのポインターを長期間保存するようなことをしないことを構文的に示唆する方法がありません。shared_ptr
また、aを返すと、誰かが単純にそれを保管し、オブジェクトの寿命を延ばす可能性が非常に高くなります。weak_ptr
しかし、を返すことは、shared_ptr
取得したものを保存することlock
は...疑わしいアイデアであることを強く示唆しています。それをやめることはありませんが、C ++ではコードを壊すことを妨げるものはありません。weak_ptr
自然なことをすることからの最小の抵抗を提供します。
さて、それは使いすぎshared_ptr
ないということではありません。確かにできます。特にpre- では、RAIIポインターを渡したりリストに入れたりする必要があるため、aを使用した場合が多くありました。ムーブセマンティクスがなければ、唯一の真の解決策でした。unique_ptr
boost::shared_ptr
unique_ptr
boost::shared_ptr
そして、あなたはそれが全く不必要な場所でそれを使うことができます。上記のように、適切なコード構造により、の一部の使用の必要性を排除できshared_ptr
ます。しかし、システムをそのように構成できず、それでも必要なことを実行できれば、shared_ptr
非常に役立ちます。
shared_ptr
c ++がPythonなどのスクリプト言語と統合されているシステムに最適です。を使用boost::python
すると、C ++およびPython側での参照カウントが大幅に協力します。C ++のオブジェクトはすべてPythonで保持でき、死ぬことはありません。
shared_ptr
。両方ともの独自の実装を使用しintrusive_ptr
ます。両方ともC ++で書かれた大規模なアプリケーションの実世界の例であるため、私はそれを取り上げます
shared_ptr
は等しく当てはまるので、あなたのコメントは非常に誤解を招くと思いますintrusive_ptr
。彼は、コンセプトの特定のスペルではなく、共有所有権のコンセプト全体に異議を唱えています。だから、この質問の目的のために、それらは、大規模なアプリケーションの二つの実世界での例ですん使用しますshared_ptr
。(さらに、shared_ptr
有効になっていない場合でも有用であることを示していweak_ptr
ます。)
使ったことがないと思うstd::shared_ptr
。
ほとんどの場合、オブジェクトはコレクションに関連付けられ、そのコレクションはライフタイム全体にわたって属します。その場合は、whatever_collection<o_type>
またはを使用するだけでwhatever_collection<std::unique_ptr<o_type>>
、そのコレクションはオブジェクトまたは自動変数のメンバーになります。もちろん、動的な数のオブジェクトが必要ない場合は、固定サイズの自動配列を使用できます。
コレクションの繰り返しやオブジェクトに対する他の操作は、所有権を共有するためのヘルパー関数を必要としません... オブジェクトを使用してから戻り、呼び出し側はオブジェクトが呼び出し全体にわたって生き続けることを保証します。これは、発信者と着信者の間で最もよく使用される契約です。
ニコルボーラスは、「あるオブジェクトが裸のポインタを保持し、そのオブジェクトが死んだら...おっと」とコメントしました。「オブジェクトは、オブジェクトがそのオブジェクトの存続期間を確実に通過することを保証する必要があります。shared_ptr
それを行うことができるだけです。」
私はその議論を買いません。少なくとも、shared_ptr
この問題は解決しません。どうですか:
ガベージコレクションと同様に、デフォルトの使用によりshared_ptr
、プログラマはオブジェクト間、または関数と呼び出し元間のコントラクトについて考えないようになります。正しい前提条件と事後条件について考えることが必要であり、オブジェクトの寿命はその大きなパイのほんの一部です。
オブジェクトは「死ぬ」ことはなく、コードの一部がそれらを破壊します。そしてshared_ptr
、通話契約を理解する代わりに問題を投げることは、誤った安全性です。
shared_ptr
ありweak_ptr
、避けるように設計されました。Bjarneは、すべてが素晴らしく明示的な寿命を持ち、すべてがその周りに構築されている世界に住もうとしています。そして、もしあなたがその世界を構築できるなら、素晴らしい。しかし、それは現実の世界ではそうではありません。オブジェクトは、そのオブジェクトがそのオブジェクトの存続期間中存続することを保証する必要があります。shared_ptr
できるのはそれだけです。
shared_ptr
特定の外部変更を1つだけ緩和し、最も一般的な変更も緩和しません。また、関数呼び出し規約で別の方法で指定されている場合、オブジェクトの寿命が正しいことを保証するのはオブジェクトの責任ではありません。
unique_ptr
、オブジェクトへのポインターが1つだけ存在し、所有権を持っていることを表すaでなければなりません。
shared_ptr
を必要とする場合でも、aを返しますunique_ptr
。からunique_ptr
への変換shared_ptr
は簡単ですが、その逆は論理的に不可能です。
私は絶対的な用語(「最後の手段」など)で考えるのではなく、問題の領域に関連するものを好みます。
C ++は、ライフタイムを管理するためのさまざまな方法を提供できます。それらのいくつかは、スタック駆動型の方法でオブジェクトを再構築しようとします。他のいくつかは、この制限を回避しようとします。それらのいくつかは「リテラル」であり、他のいくつかは近似です。
実際にできること:
Person
それを有するがname
同じ人物(:同じの2つの表現より良い人)。寿命はマシンスタックによって付与され、プログラムの終了は本質的に重要ではありません(人はそれが何であるかに関係なく、名前であるPerson
ため)std::unique_ptr
はそれを行います(サイズ1のベクトルと考えることができます)。繰り返しになりますが、オブジェクトが参照するデータ構造の前(後)にオブジェクトが存在し始める(そして存在が終わる)ことを認めます。この方法の弱点は、オブジェクトの種類と量が、作成される場所に関してより深いスタックレベルの呼び出しの実行中に変化できないことです。この手法はすべて、オブジェクトの作成と削除がユーザーアクティビティの結果であるすべての状況で強度を「失敗」させるため、オブジェクトのランタイムタイプはコンパイル時に認識されず、オブジェクトを参照する構造が過剰になる可能性があります。ユーザーは、より深いスタックレベルの関数呼び出しから削除するように求めています。この場合、次のいずれかを行う必要があります。
C ++ isteslfにはwhile(are_they_needed)
、そのイベントを監視するネイティブメカニズムがありません()。したがって、次のように近似する必要があります。
最後の解決策の最初の解決策に進むと、オブジェクトのライフタイムを管理するために必要な補助データ構造の量が増加し、オブジェクトの整理と保守に時間がかかるようになります。
ガベージコレクターにはコストがあり、shared_ptrにはさらに少なく、unique_ptrにはさらに少なく、スタック管理オブジェクトにはほとんどありません。
であるshared_ptr
「最後の手段」?。いいえ、そうではありません。最後の手段はガベージコレクターです。
shared_ptr
実際にstd::
提案されている最後の手段です。しかし、あなたが私が説明した状況にいるなら、適切な解決策かもしれません。
後のセッションでハーブサッターが言及したことの1つは、をコピーするたびにshared_ptr<>
、連動する増分/減分が発生するということです。マルチコアシステム上のマルチスレッドコードでは、メモリの同期は重要ではありません。選択肢があれば、スタック値を使用するか、またはunique_ptr<>
参照または生のポインターを渡すことをお勧めします。
shared_ptr
...左辺値または右辺値参照で
shared_ptr
なのは、標準にあるからといってメモリリークの問題をすべて解決するのは、特効薬のように使用しないことです。これは魅力的なトラップですが、リソースの所有権を認識することは依然として重要であり、その所有権が共有されない限り、a shared_ptr<>
は最良の選択肢ではありません。
最後の「リゾート」が彼が使用した正確な単語であったかどうかは覚えていませんが、彼が言ったことの実際の意味は最後の「選択」であったと信じています。unique_ptr、weak_ptr、shared_ptr、さらにはネイキッドポインターにも場所があります。
彼らが同意したことの1つは、私たち(開発者、本の著者など)がすべてC ++ 11の「学習段階」にあり、パターンとスタイルが定義されているということです。
例として、Herbは、Cでの業界の経験とベストプラクティスから数年後に、Effective C ++(Meyers)やC ++ Coding Standards(Sutter&Alexandrescu)などのいくつかの重要なC ++ブックの新しいエディションを期待すべきだと説明しました。 ++ 11はパンアウトします。
彼が得ているのは、標準的なポインタ(グローバルな置換のようなもの)を書いたときはいつでもshared_ptrを書くことが誰にでも一般的になり、実際に設計するのではなく、少なくともオブジェクトの作成と削除の計画。
人々が忘れているもう1つのことは(上記の資料で述べたロック/更新/ロック解除のボトルネック以外に)、shared_ptrだけではサイクルの問題を解決できないということです。shared_ptrでリソースをリークする可能性はあります:
オブジェクトAは、別のオブジェクトAへの共有ポインターを含みます。オブジェクトBは、A a1とA a2を作成し、a1.otherA = a2を割り当てます。およびa2.otherA = a1; これで、a1、a2の作成に使用したオブジェクトBの共有ポインターが範囲外になります(関数の最後など)。これでリークが発生しました。他の誰もa1とa2を参照していませんが、互いを参照しているため、参照カウントは常に1であり、リークしています。
これは簡単な例です。実際のコードでこれが発生すると、通常は複雑な方法で発生します。weak_ptrには解決策がありますが、非常に多くの人がどこでもshared_ptrを実行するだけで、リークの問題やweak_ptrのことすら知りません。
まとめると、OPが参照するコメントは次のように要約されると思います。
使用している言語(管理対象、管理対象外、またはshared_ptrなどの参照カウントの中間)に関係なく、オブジェクトの作成、存続期間、および破棄を理解し、意図的に決定する必要があります。
編集:それが「不明、shared_ptrを使用する必要がある」という意味であっても、あなたはそれをまだ考えており、意図的にそうしています。
すべてのオブジェクトが参照カウントされてヒープに割り当てられる言語であるObjective-Cの経験からお答えします。オブジェクトを処理する方法が1つあるため、プログラマにとって物事はずっと簡単です。これにより、標準の規則を定義することができます。この規則に従うと、コードの堅牢性とメモリリークが保証されます。また、最新のARC(自動参照カウント)のように、巧妙なコンパイラー最適化が可能になりました。
私のポイントは、shared_ptrが最後の手段ではなく最初の選択肢であるべきだということです。既定で参照カウントを使用するか、他のオプションを使用するのは、実行していることが確実な場合のみにしてください。生産性が向上し、コードがより堅牢になります。
私は質問に答えようとします:
std :: shared_ptrを使用せずにプログラミングし、オブジェクトの有効期間を安全に管理するにはどうすればよいですか?
C ++には、メモリを実行するためのさまざまな方法が多数あります。次に例を示します。
struct A { MyStruct s1,s2; };
クラススコープでshared_ptrの代わりに使用します。これは、依存関係がどのように機能するかを理解する必要があり、依存関係をツリーに制限するのに十分な依存関係を制御する必要があるため、上級プログラマー専用です。ヘッダーファイル内のクラスの順序は、これの重要な側面です。この使用法は、ネイティブの組み込みc ++型では既に一般的ですが、プログラマ定義のクラスでの使用は、これらの依存関係とクラスの順序の問題のため、あまり使用されていないようです。このソリューションには、sizeofにも問題があります。プログラマーは、この問題を前方宣言または不必要な#includeを使用するための要件と見なしているため、多くのプログラマーはポインターの下位ソリューションに戻り、後でshared_ptrに戻ります。MyClass &find_obj(int i);
代わりに+ clone()を使用しshared_ptr<MyClass> create_obj(int i);
ます。多くのプログラマーは、新しいオブジェクトを作成するためのファクトリーを作成したいと考えています。shared_ptrは、この種の使用に最適です。問題は、単純なスタックまたはオブジェクトベースのソリューションではなく、ヒープ/フリーストア割り当てを使用した複雑なメモリ管理ソリューションを既に想定していることです。優れたC ++クラス階層は、1つだけでなく、すべてのメモリ管理スキームをサポートします。参照ベースのソリューションは、ローカル関数スコープ変数を使用する代わりに、返されたオブジェクトが包含オブジェクト内に格納されている場合に機能します。所有権を工場からユーザーコードに渡すことは避けてください。find_obj()を使用した後のオブジェクトのコピーは、オブジェクトを処理するための良い方法です。通常のコピーコンストラクターおよび通常のコンストラクター(異なるクラス)のrefrerenceパラメーターまたは多相オブジェクトのclone()で処理できます。unique_ptr
工場に最適です。あなたは変えることができますunique_ptr
にshared_ptr
、それは他の方向に行くことを論理的に不可能です。