回答:
さまざまなアプローチを試みた後、今日私はGoogle C ++スタイルガイドと調和していることに気付きました。
実際にポインターのセマンティクスが必要な場合、scoped_ptrは素晴らしいです。std :: tr1 :: shared_ptrは、オブジェクトをSTLコンテナで保持する必要がある場合など、非常に特殊な条件下でのみ使用してください。auto_ptrを使用しないでください。[...]
一般的に、明確なオブジェクト所有権を使用してコードを設計することを好みます。オブジェクトをフィールドまたはローカル変数として直接使用することで、ポインターをまったく使用せずに、オブジェクトの所有権が最も明確になります。[..]
推奨されていませんが、参照カウントポインターは、問題を解決するための最も簡単で最も洗練された方法である場合があります。
また、私は「強い所有権」の思考の流れに従います。必要に応じて、「このクラスがこのメンバーを所有している」ことを明確に描写したいと思います。
私はめったに使用しませんshared_ptr
。使用する場合はweak_ptr
、参照カウントを増やすのではなく、オブジェクトへのハンドルのように扱うことができるように、可能な限り自由に使用します。
私はあちこちで使っていscoped_ptr
ます。明らかな所有権を示しています。そのようなオブジェクトをメンバーにしないのは、scoped_ptrにあるオブジェクトを前方宣言できるからです。
オブジェクトのリストが必要な場合は、を使用しますptr_vector
。を使用するよりも効率的で副作用が少ないvector<shared_ptr>
です。あなたはptr_vectorで型を前方宣言できないかもしれません(しばらくの間)が、その意味論は私の意見では価値があります。基本的に、リストからオブジェクトを削除すると、自動的に削除されます。これは明らかな所有権も示しています。
何かへの参照が必要な場合は、ネイキッドポインターではなく参照にしようとします。これは実用的でない場合があります(つまり、オブジェクトの構築後に参照が必要な場合)。いずれにせよ、参照は明らかにあなたがオブジェクトを所有していないことを示しており、他の場所で共有ポインタのセマンティクスを使用している場合、通常、裸のポインタは追加の混乱を引き起こしません(特に「手動削除なし」ルールに従う場合) 。
この方法で、私が取り組んだiPhoneゲームの1つは1回しかdelete
呼び出すことができず、それは私が書いたObj-CからC ++へのブリッジにありました。
一般的に、記憶管理は人間に任せるには重要すぎると私は考えています。削除を自動化できる場合は、そうする必要があります。shared_ptrのオーバーヘッドが実行時に高すぎる場合(スレッドのサポートをオフにしたなど)、おそらく他の何か(バケットパターンなど)を使用して動的割り当てを下げる必要があります。
ジョブに適したツールを使用します。
プログラムで例外をスローできる場合は、コードが例外に対応していることを確認してください。スマートポインター、RAIIの使用、および2フェーズ構成の回避は、出発点として適切です。
明確な所有権セマンティクスのない循環参照がある場合は、ガベージコレクションライブラリの使用またはデザインのリファクタリングを検討できます。
優れたライブラリを使用すると、タイプではなくコンセプトにコーディングできるため、ほとんどの場合、リソース管理の問題を超えてどの種類のポインターを使用してもかまいません。
マルチスレッド環境で作業している場合、オブジェクトがスレッド間で共有される可能性があるかどうかを必ず確認してください。boost :: shared_ptrまたはstd :: tr1 :: shared_ptrの使用を検討する主な理由の1つは、スレッドセーフな参照カウントを使用するためです。
参照カウントの個別の割り当てが心配な場合は、これを回避する方法がたくさんあります。boost :: shared_ptrライブラリを使用して、参照カウンターをプールするか、boost :: make_shared(my preference)を使用して、オブジェクトと参照カウントを単一の割り当てで割り当てることで、キャッシュミスに関するほとんどの懸念を軽減できます。最上位レベルでオブジェクトへの参照を保持し、オブジェクトへの直接参照を渡すことにより、パフォーマンスが重要なコードの参照カウントを更新することによるパフォーマンスの低下を回避できます。
共有所有権が必要であるが、参照カウントまたはガベージコレクションのコストを支払いたくない場合は、不変オブジェクトまたはコピーオンライトイディオムの使用を検討してください。
パフォーマンスの最大の勝利は、アーキテクチャレベルであり、その後にアルゴリズムレベルが続くことを念頭に置いてください。これらの低レベルの懸念は非常に重要ですが、主要な問題に対処した後にのみ対処する必要があります。キャッシュミスのレベルでパフォーマンスの問題を処理している場合は、多くの問題が発生します。これらの問題は、たとえば、ポインタごとに関係のない偽共有のように注意する必要があります。
テクスチャやモデルなどのリソースを共有するためだけにスマートポインターを使用している場合は、Boost.Flyweightなどのより特殊なライブラリを検討してください。
新しい標準が採用されると、移動セマンティクス、右辺値参照、および完全な転送により、高価なオブジェクトおよびコンテナの操作がはるかに簡単かつ効率的になります。それまでは、auto_ptrやunique_ptrなどの破壊的なコピーセマンティクスを持つポインターをコンテナー(標準の概念)に保存しないでください。Boost.Pointer Containerライブラリを使用するか、共有所有権スマートポインターをContainersに保存することを検討してください。パフォーマンスが重要なコードでは、Boost.Intrusiveのような侵入型コンテナーを優先して、これらの両方を回避することを検討できます。
ターゲットプラットフォームが決定にあまり影響を与えてはいけません。組み込みデバイス、スマートフォン、ダム電話、PC、およびコンソールはすべて、コードを問題なく実行できます。厳密なメモリバジェットやロード前後の動的割り当てがないなどのプロジェクト要件は、より有効な懸念事項であり、選択に影響するはずです。
C ++ 0xを使用している場合は、を使用しますstd::unique_ptr<T>
。
std::shared_ptr<T>
参照カウントのオーバーヘッドとは異なり、パフォーマンスのオーバーヘッドはありません。unique_ptr はそのポインターを所有しており、C ++ 0xのmoveセマンティクスを使用して所有権を転送できます。コピーすることはできません-移動するだけです。
また、コンテナ内で使用することもできます。たとえばstd::vector<std::unique_ptr<T>>
、バイナリ互換であり、パフォーマンスと同じですが、std::vector<T*>
要素を消去したりベクトルをクリアしてもメモリリークは発生しません。これは、STLアルゴリズムとの互換性も優れていますptr_vector
。
多くの目的のためのIMOは、これが理想的なコンテナです。ランダムアクセス、例外セーフ、メモリリークの防止、ベクトルの再割り当てのオーバーヘッドの削減(舞台裏でポインタをシャッフルするだけ)。多くの目的に非常に役立ちます。
どのクラスがどのポインターを所有しているかを文書化することをお勧めします。できれば、通常のオブジェクトのみを使用し、ポインタは使用しないようにしてください。
ただし、リソースを追跡する必要がある場合は、ポインターを渡すことが唯一のオプションです。いくつかのケースがあります:
私は今、自分のリソースをどのように管理するかをほぼカバーしていると思います。shared_ptrのようなポインターのメモリコストは、通常、通常のポインターのメモリコストの2倍です。このオーバーヘッドが大きすぎるとは思いませんが、リソースが少ない場合は、スマートポインターの数を減らすようにゲームを設計することを検討してください。他のケースでは、上記の箇条書きのような良い原則に合わせて設計するだけで、プロファイラーはどこにもっとスピードが必要かを教えてくれます。
ブーストのポインターに関しては、その実装が必要なものと正確に一致しない限り、避けるべきだと思います。彼らは、誰もが最初に期待するよりも大きいコストで来ます。これらは、メモリおよびリソース管理の重要で重要な部分をスキップできるインターフェイスを提供します。
ソフトウェア開発に関しては、あなたのデータについて考えることが重要だと思います。メモリ内でデータがどのように表されるかは非常に重要です。これは、CPUアクセス速度がメモリアクセス時間よりもずっと速い速度で増加しているためです。これにより、多くの場合、メモリキャッシュが最新のコンピューターゲームの主なボトルネックになります。アクセス順序に従ってメモリ内でデータを線形に整列させることにより、キャッシュがより使いやすくなります。この種のソリューションは、多くの場合、よりクリーンなデザイン、よりシンプルなコード、より明確にデバッグしやすいコードにつながります。スマートポインターを使用すると、リソースの頻繁な動的メモリ割り当てが容易に発生するため、メモリ全体に散在します。
これは時期尚早な最適化ではなく、可能な限り早期に行うことができる、また行うべき健全な決定です。これは、ソフトウェアが実行されるハードウェアのアーキテクチャー理解の問題であり、重要です。
編集:共有ポインターのパフォーマンスに関して考慮すべきことがいくつかあります。
私はどこでもスマートポインターを使用する傾向があります。これが完全に良いアイデアであるかどうかはわかりませんが、私は怠け者であり、本当の欠点はありません(Cスタイルのポインター演算を行いたい場合を除く)。boost :: shared_ptrを使用するのは、コピーできることを知っているからです。2つのエンティティが画像を共有している場合、一方が死んでも他方が画像を失うことはありません。
これの欠点は、あるオブジェクトが指し示し所有しているものを削除するが、別のオブジェクトもそれを指している場合、削除されないことです。
私は年寄りで、オールドスクールで、サイクルカウンターです。私自身の仕事では、実行時に生のポインタを使用し、動的割り当ては使用しません(プール自体を除く)。すべてがプールされ、所有権は非常に厳格であり、譲渡することはできません。本当に必要な場合は、カスタムの小さなブロックアロケーターを作成します。すべてのプールがゲームをクリアするための状態になっていることを確認します。物事が毛むくじゃらになったら、オブジェクトをハンドルでラップして再配置できるようにしますが、そうではありません。コンテナはカスタムであり、非常にむき出しの骨です。また、コードを再利用しません。
すべてのスマートポインター、コンテナー、イテレーターなどの美徳について議論することは決してありませんが、非常に高速にコーディングできることで知られています(そして、かなり信頼できる-ある程度明白な理由で他の人が私のコードに飛び込むことはお勧めできませんが、心臓発作や恒久的な悪夢のような)。
仕事ではもちろん、プロトタイピングをしていない限り、すべてが異なります。
確かにこれは奇妙な答えであり、おそらく誰にも適しているとは言えません。
しかし、特定のタイプのすべてのインスタンスを中央のランダムアクセスシーケンス(スレッドセーフ)に保存し、代わりに32ビットインデックス(相対アドレス、つまり) 、絶対ポインターではなく。
はじめに:
T
メモリ内に散らばることがなくなります。これにより、すべての種類のアクセスパターンのキャッシュミスが減少する傾向があります。ノードがポインターではなくインデックスを使用してリンクされている場合、ツリーなどのリンクされた構造を横断することもあります。とはいえ、利便性は型の安全性だけでなく欠点でもあります。コンテナとインデックスの両方にT
アクセスしなければ、インスタンスにアクセスできません。そして、単純な古いものは、それがどのデータ型を参照しているかについて何も教えてくれません。へのインデックスを使用して、誤ってにアクセスしようとする可能性があります。2番目の問題を軽減するために、私はしばしばこのようなことをします。int32_t
Bar
Foo
struct FooIndex
{
int32_t index;
};
これはちょっとばかげているように見えますが、型エラーが発生するので、コンパイラエラーなしで誤ってBar
インデックスを介してにアクセスしようとすることはありませんFoo
。利便性のために、私はわずかな不便を受け入れます。
他の人にとって大きな不便なことは、OOPスタイルの継承ベースのポリモーフィズムを使用できないことです。これは、サイズと配置の要件が異なるすべての種類のサブタイプを指すことができるベースポインターを必要とするためです。しかし、最近では継承をあまり使用しません。ECSアプローチを好みます。
についてはshared_ptr
、あまり使わないようにしています。ほとんどの場合、所有権を共有するのは理にかなっていないと思います。多くの場合、少なくとも高レベルでは、1つのものが1つのものに属する傾向があります。私が頻繁に使用shared_ptr
したいと思ったのは、スレッドが終了する前にオブジェクトが破壊されないようにするためのスレッドのローカル関数のように、所有権をあまり扱っていない場所でオブジェクトの寿命を延ばすことでしたそれを使用します。
その問題に取り組むために、shared_ptr
またはGCなどを使用する代わりに、スレッドプールから実行される短命のタスクを優先し、そのスレッドがオブジェクトの破棄を要求した場合、実際の破棄は安全に延期されるようにしますシステムが、スレッドが上記のオブジェクトタイプにアクセスする必要がないことを保証できる時間。
参照カウントを使用することもありますが、最後の手段として扱います。そして、永続的なデータ構造の実装のように、所有権を共有することが本当に理にかなっている場合がいくつかありますが、shared_ptr
すぐに手を伸ばすことが完全に理にかなっていることがわかります。
とにかく、私は主にインデックスを使用し、生とスマートの両方のポインターを控えめに使用します。インデックスと、オブジェクトが連続して格納され、メモリ空間に散らばっていないことがわかっているときに開くインデックスの種類が好きです。