「Going Native 2012」のストリームを見ていると、std::shared_ptr
に関する議論に気づきました。 Bjarneのstd::shared_ptr
に対するいくらか否定的な見方と、オブジェクトの寿命が不確かな場合に「最後の手段」として使用すべきであるという彼のコメントを聞いて少し驚いた(彼によれば、ケースです)。
誰かがこれをもう少し詳しく説明したいと思いますか?どのようにstd::shared_ptr
なしでプログラミングし、オブジェクトのライフタイムをsafeの方法で管理できますか?
所有権の共有を回避できる場合、アプリケーションはシンプルで理解しやすくなり、メンテナンス中に発生するバグの影響を受けにくくなります。所有権モデルが複雑または不明確であると、簡単に追跡できない共有状態を介して、アプリケーションのさまざまな部分の結合を追跡することが困難になる傾向があります。
このため、自動保存期間を持つオブジェクトを使用し、「値」サブオブジェクトを作成することをお勧めします。これに失敗すると、unique_ptr
はshared_ptr
存在すること-最後の手段ではないにしても-望ましいツールのリストの下にある方法で。
std::shared_ptr
を使用したことがないと思います。
ほとんどの場合、オブジェクトは、その存続期間全体が属するコレクションに関連付けられています。その場合は、whatever_collection<o_type>
またはwhatever_collection<std::unique_ptr<o_type>>
を使用できます。そのコレクションは、オブジェクトまたは自動変数のメンバーです。もちろん、動的な数のオブジェクトが必要ない場合は、固定サイズの自動配列を使用できます。
コレクションの反復やオブジェクトに対するその他の操作のいずれも、所有権を共有するためのヘルパー関数を必要としません...それオブジェクトを使用してから戻り、呼び出し元は、オブジェクトが呼び出し全体にわたって存続することを保証します 。これは、呼び出し元と呼び出し先の間で最も使用される契約です。
Nicol Bolasは、「あるオブジェクトがネイキッドポインターをつかんでいると、そのオブジェクトが死ぬと……おっと」とコメントしました。および「オブジェクトは、オブジェクトがそのオブジェクトの存続期間にわたって存続することを保証する必要があります。それを実行できるのはshared_ptr
のみです。」
私はその議論を買いません。少なくともshared_ptr
はこの問題を解決しません。何について:
ガベージコレクションと同様に、shared_ptr
をデフォルトで使用すると、プログラマーはオブジェクト間、または関数と呼び出し元の間のコントラクトについて考える必要がなくなります。正しい前提条件と事後条件について考える必要があり、オブジェクトの存続期間はその大きなパイのほんの一部です。
オブジェクトは「死ぬ」ことはなく、コードの一部がそれらを破壊します。そして、コールコントラクトを理解するのではなく、問題にshared_ptr
を投げることは誤った安全です。
私は(「最後の手段」のように)絶対的な用語で考えるのではなく、問題の領域に関連して考えます。
C++は、寿命を管理するためのさまざまな方法を提供できます。それらのいくつかは、スタック駆動の方法でオブジェクトを再変換しようとします。他のいくつかはこの制限を回避しようとします。それらのいくつかは「リテラル」であり、他のいくつかは近似です。
実際にできること:
Person
を持つ2つのname
は同じであると想定できますperson(より良い:同じpersonの2つの表現)。ライフタイムはマシンスタックによって付与され、プログラムの終了は本質的に重要ではありません((personなので)name、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<>
のいずれかを使用して、参照または生のポインタを渡すのが適切です。
最後の「リゾート」が彼が使用した正確な言葉であったかどうかは覚えていませんが、彼の言ったことの実際の意味は最後の「選択」だったと思います。 unique_ptr、weak_ptr、shared_ptr、さらにはネイキッドポインターさえもその位置を占めています。
彼ら全員が同意したことの1つは、私たち(開発者、本の作者など)がすべてC++ 11の「学習段階」にあり、パターンとスタイルが定義されていることです。
例として、ハーブは Effective C++ (Meyers)や C++ Coding Standards (Sutter&Alexandrescu)などの精巧なC++書籍の新版を期待すべきだと説明しました。 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を使用する必要がある」という意味であっても、あなたはまだそれを考えており、意図的にそうしています。
allオブジェクトが参照カウントされ、ヒープに割り当てられる言語であるObjective-Cでの経験から答えます。オブジェクトを処理する方法が1つしかないため、プログラマにとってははるかに簡単です。これにより、標準のルールを定義することが可能になり、準拠すると、コードの堅牢性が保証され、メモリリークが発生しなくなります。また、最近のARC(自動参照カウント)のように、巧妙なコンパイラーの最適化が可能になりました。
私のポイントは、shared_ptrが最後の手段ではなく、最初のオプションであるべきだということです。デフォルトでの参照カウントとその他のオプションは、自分が何をしているのかがはっきりしている場合にのみ使用してください。生産性が向上し、コードがより堅牢になります。
私は質問に答えようとします:
どのようにしてstd :: shared_ptrなしでプログラミングし、オブジェクトのライフタイムを安全に管理できますか?
C++には、メモリを実行するさまざまな方法が多数あります。次に例を示します。
struct A { MyStruct s1,s2; };
_を使用します。依存関係がどのように機能するかを理解する必要があり、依存関係をツリーに制限するのに十分な依存関係を制御する機能が必要であるため、これは上級プログラマ向けです。ヘッダーファイル内のクラスの順序は、これの重要な側面です。この使用法は、ネイティブの組み込みc ++型では既に一般的ですが、プログラマー定義クラスでの使用は、これらの依存関係とクラスの順序の問題のため、あまり使用されていないようです。このソリューションは、sizeofにも問題があります。プログラマーはこの問題を、前方宣言または不要な#includeを使用する必要があると見なしているため、多くのプログラマーは、ポインターの劣ったソリューションにフォールバックし、後でshared_ptrにフォールバックします。MyClass &find_obj(int i);
の代わりにshared_ptr<MyClass> create_obj(int i);
+ clone()を使用してください。多くのプログラマーは、新しいオブジェクトを作成するためのファクトリーを作成したいと考えています。 shared_ptrは、この種の使用法に最適です。問題は、単純なスタックやオブジェクトベースのソリューションではなく、ヒープ/フリーストアの割り当てを使用する複雑なメモリ管理ソリューションをすでに想定していることです。優れたC++クラス階層は、そのうちの1つだけでなく、すべてのメモリ管理スキームをサポートします。参照ベースのソリューションは、ローカル関数スコープ変数を使用する代わりに、返されたオブジェクトが包含オブジェクト内に格納されている場合に機能します。所有権をファクトリからユーザーコードに渡すことは避けてください。 find_obj()を使用した後にオブジェクトをコピーすることは、オブジェクトを処理するための良い方法です。通常のコピーコンストラクターと、異なるパラメーターの通常のコンストラクターを使用して、多相性オブジェクトのrefrerenceパラメーターまたはclone()で処理できます。