C ++イテレーターの存続期間と無効化の検出


8

C ++ 11の慣用的なものに基づいて:

  • カスタムコンテナーへのイテレーターは、コンテナー自体が破棄されても存続しますか?
  • イテレータが無効になったときにそれを検出することは可能でしょうか?
  • 上記は「デバッグビルド」を実際に条件としていますか?

詳細:私は最近C ++をブラッシュアップし、C ++ 11の使い方を学びました。その一環として、私はuriparserライブラリーの慣用的なラッパーを作成しています。この一部は、解析されたパスコンポーネントのリンクリスト表現をラップすることです。コンテナーの慣用句についてのアドバイスを探しています。

最近気になるのは、ガベージコレクションされた言語から来ていることですが、ランダムなオブジェクトが、ライフタイムに関して間違いを犯した場合に、ユーザーに表示されなくなるだけではありません。これを説明するために、PathListコンテナとその反復子の両方shared_ptrが実際の内部状態オブジェクトを保持します。これにより、そのデータを指すものが存在する限り、データも存在するようになります。

ただし、STL(および多くの検索)を見ると、C ++コンテナーがこれを保証しているようには見えません。私はこの恐ろしい疑いを抱いており、期待はコンテナーを破壊するだけで、それに伴うイテレーターをすべて無効にすることです。 std::vector確かにイテレータを無効にしても機能するようです。

私が知りたいのは、「良い」/慣用的なC ++ 11コードから何が期待されるかです。光沢のある新しいスマートポインターを考えると、STLを使用すると、誤ってイテレーターをリークして足を簡単に吹き飛ばせるのは奇妙に思えます。shared_ptrバッキングデータを使用することは、不必要な非効率性、デバッグのための良いアイデア、またはSTLが実行しないと予想されることですか?

(これを「慣用的なC ++ 11」に接地することで、主観性の訴えが回避されることを願っています...)

回答:


10

shared_ptrバッキングデータを不必要に非効率的に使用している

はい-要素ごとに追加の間接指定と追加の割り当てを強制します。マルチスレッドプログラムでは、特定のコンテナーが単一のスレッド内でのみ使用されている場合でも、参照カウントの増分または減分はそれぞれ非常に高価です。

状況によっては、これらはすべて問題なく、望ましい場合さえありますが、一般的なルールは、ユーザー使用できない場合でも、ユーザーが避けられない不必要なオーバーヘッドを課さないことです。

これらのオーバーヘッドのどれも必要ありませんが、むしろ細かい点をデバッグしているので(と覚えて、間違ったイテレータ寿命は、静的なロジックのバグではなく、いくつかの奇妙な実行時の動作である)、誰が彼らの減速をお願い致しないだろう正しいキャッチするためのコードを、あなたのバグを。


だから、元の質問に:

カスタムコンテナーへのイテレーターは、コンテナー自体が破棄されても存続しますか?

本当の問題は、すべてのライブイテレータをコンテナに追跡し、コンテナが破棄されたときにそれらを無効にするコストを、コードが正しい人に当てはめる必要があるかどうかです。

おそらくそうではないと思いますが、イテレータのライフタイムを正しく管理することが本当に困難で、ヒットしても構わない場合は、このサービスを提供する専用コンテナ(またはコンテナアダプタ)をオプションとして追加できます

または、コンパイラフラグに基づいてデバッグ実装に切り替えるのは妥当かもしれませんが、これは、DEBUG / NDEBUGによって制御されるほとんどの変更よりもはるかに大きくてコストのかかる変更です。これは確かに、assertステートメントを削除したり、デバッグアロケータを使用したりするよりも大きな変更です。


言及するのを忘れていましたが、shared_ptrあらゆる場所で使用するという解決策では、必ずしもバグが修正されるとは限りません。単に、それを別のバグ、つまりメモリリーク交換するだけの可能性があります。


「すべてのライブイテレーターをコンテナーに追跡し、コンテナーが破棄されたときにそれらを無効にするコストは、コードが正しい人に頼る必要がありますか?」まったくない。あなたの投稿が示すように、C ++の事実上のモットーの1つは、「使用しないものに対してはお金を払わない」です。これは非常に正当な理由によるものです。もし悪いプログラマーがするかもしれないすべての些細なことに対してセンスチェックをしなければならないなら、それは多くのよくプログラムされたプロジェクトを妨げます。しかし、もちろん、あなたが示したように、誰かが本当にそれを望んでいるなら...彼らはそれを自分で実装する(そしてそれを維持する)ツールを持っています。両方の長所!
underscore_d

7

C ++では、コンテナーを破棄させると、イテレーターが無効になります。少なくとも、これはイテレータが役に立たないことを意味し、それを逆参照しようとすると、多くの悪いことが起こり得ます(実際にはどの程度悪いかは実装に依存しますが、通常はかなり悪いです)。

C ++のような言語では、そのようなことを正しく行うのはプログラマの責任です。これは言語の強みの1つです。これは、物事がいつ発生するか(オブジェクトを削除したかどうか)に大きく依存できるためです。つまり、削除の瞬間にデストラクタが呼び出され、メモリが解放されます。その上)、しかし、それはまた、あなたがイテレータをあらゆる場所のコンテナに保持し続けることができず、そのコンテナを削除することができないことも意味します。

では、イテレータがすべてなくなるまでデータを保持するコンテナを作成できますか?もちろん、あなたはその通りに進んでいます。これは通常のC ++の方法ではありませんが、適切に文書化されている(そしてもちろんデバッグされている)限り、問題はありません。それは、STLコンテナーの動作方法ではありません。


1
悪いことは、センチネルを未定義の行動に戻すことから行くことができることに注意してください
ラチェットフリーク

@ratchetfreak-はい、そうです。問題の場合(コンテナーへのイテレーター)には、通常、センチネル値を定義する適切な方法がないため、通常のC ++の方法(およびSTLの動作)は「未定義の動作」に向かう傾向があります。
Michael Kohne

5

C ++言語とGC言語の(多くの場合は言われていません)違いの1つは、主流のC ++イディオムがすべてのクラスが値クラスであると想定していることです。

ポインターと参照がありますが、それらは、ポリモーフィックディスパッチ(仮想関数の間接参照による)の許可や、それらを作成したブロックの1つを存続させる必要のあるオブジェクトの管理に主に追いやられています。

この最後のケースでは、誰がいつ誰をいつ破壊するかについてのポリシーと政治を定義するのはプログラマの責任です。スマートポインタ(shared_ptrまたはunique_ptr)は、オブジェクトがさまざまな所有者によって共有されている(そして最後のオブジェクトを破棄したい)場合や、コンテキスト間で移動する必要がある場合に、このタスクを支援するツールにすぎません常に単一のコンテキストがそれを所有している。

インターレーターは、設計上、反復中にのみ意味を持ちます。したがって、それらが参照するものは、そのままであるか、そこにとどまることが許可されていないため、「後で使用するために保存する」べきではありません(コンテナーは、拡大または縮小するときのコンテンツ...すべてを無効にします)。リンクベースのコンテナ(listsなど)は、この一般的なルールの例外であり、ルール自体の例外ではありません。

慣用的なC ++では、AがBを「必要とする」場合、BはAを所有する場所よりも長生きする場所に所有する必要があるため、AからのBの「ライフトラッキング」は必要ありません。

shared_ptrそしてweak_ptr、「私たち全員があなたを許可するまで離れないでください」または「あなたが離れるなら私たちにメッセージを残してください」というポリシーをそれぞれ許可することによって、このイディオムが過度に制限されている場所を助けてください。しかし、そうするためには、補助データを割り当てる必要があるため、コストがかかります。

次のステップはgc_ptr-sです(標準ライブラリでは提供されていませんが、必要に応じて、たとえばマーク&スイープアルゴリズムを使用して実装できます)。追跡構造はさらに複雑で、よりプロセッサに負荷がかかります。彼らのメンテナンス。


4

C ++では、次のことを行うことは慣用的です

  • 注意深いコーディングと
  • から保護するためにランタイムコストが発生します

未定義の動作

イテレータの特定のケースでは、各コンテナのドキュメントに、イテレータを無効にする操作(コンテナの破壊は常にその中にある)が記載されており、無効なイテレータへのアクセスは未定義の動作です。実際には、ランタイムがもはや有効でないポインタに盲目的にアクセスすることを意味します。通常はクラッシュしますが、メモリが破損し、予期しない結果になる可能性があります。

デバッグモードでオンにできるオプションのチェックを提供します(以下の#define場合、デフォルトでオンになります)_DEBUG定義さは定義さは無効になるNDEBUG)ことをお勧めします。

ただし、C ++はパフォーマンスのあらゆるビットが必要なケースを処理するように設計されており、イテレータはタイトループで使用されることが多いため、チェックにかなりのコストがかかる場合があるため、デフォルトでは有効にしないでください。

私たちの作業プロジェクトでは、一部のコンテナーが他のコンテナーとイテレーターを内部で使用し、1つの巨大なコンテナーを破棄するだけでチェックのために30分かかっていたため、デバッグモードでもMicrosoft標準ライブラリのイテレーターチェックを無効にする必要がありました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.