4

std::vector とは異なり、初期化されていないストレージを許可するベクターコンテナーを構築したいとしましょう。たとえば、コンテナの使用法は、vec <T>おおよそ次のようになります。

  • ユーザーは、ベクトルが N 個の初期化されていない要素を次のように割り当てる必要があると明示的に述べています。

    vec <T> a(N, no_init);

  • データが既知の時点で、ユーザーはn引数を使用して position で要素を明示的に初期化しますargs...

    a.init(n, args...);

  • または、同等に、要素を手動で構築します。

    new (&a[n]) T(args...);

  • 他の操作は、より大量に初期化またはコピーする場合があります ( などstd::uninitialized_copy) が、これは便宜上のものにすぎません。基本的な操作は同じです。

  • いくつかのタスクを完了した後、ベクトルには初期化された要素と初期化されていない要素が残る場合があります。ベクトルは余分な情報を保持しないため、最終的にはメモリを解放する前に、とにかくすべての要素を破棄するか、に応じてのみ破棄しTます。

私はこれができると確信していますが、結果についてはわかりません。T当然のことながら、ユーザーが構築前に初期化されていない要素を使用しようとしないことを前提として、この構造がすべての型に対して安全であることを望みます。これは強い仮定のように聞こえるかもしれませんが、ベクトルの範囲内でのみ要素にアクセスすることは、それほど異なる仮定ではなく、非常に一般的です。

だから私の質問は:

  1. のように、この種の初期化されていない操作を許可しても安全なのはどのタイプvec <T> a(no_init)ですか? 私is_podは大丈夫だと思いますし、おそらくそうis_trivialです。必要以上の制約を加えたくありません。

  2. 破棄は常に実行する必要がありますか、それとも一部の型に対してのみ実行する必要がありますか? 上記と同じ制約で問題ありませんか? どうis_trivially_destructibleですか?構築されていない要素を破棄したり、その逆 (構築された要素を破棄しない) が害を及ぼすべきではないという考え方です。

  3. ユーザーにより多くの責任を負わせる明らかなリスク以外に、この試みに重大な欠陥はありますか?

要点は、ユーザーがパフォーマンスのためにそのような機能を必要とする場合、低レベルのソリューションstd::get_temporary_bufferや手動割り当て (たとえば を使用operator new()) は、リークに関してよりリスクが高い可能性があるということです。私は知ってstd::vector::emplace_back()いますが、それは実際には同じことではありません。

4

1 に答える 1

2

質問に答えるには:

  1. 制限なしT: 標準コンテナで機能する場合は、あなたのコンテナでも機能します。
  2. 破壊は条件付きですstd::is_trivially_destructible<T>。そうでない場合は、構築された要素を追跡し、実際に構築された要素のみを削除する必要があります。
  3. あなたのアイデアに大きな欠陥は見当たりませんが、それだけの価値があることを確認してください。ユースケースのプロファイルを作成し、要素の初期化に実際に多くの時間を費やしていることを確認してください。

size の連続したメモリのブロックとしてコンテナを実装すると仮定していますsize() * sizeof(T)。また、要素のデストラクタを呼び出す必要がある場合、つまり、要素の破壊のフラグを立てるために使用する要素のように!std::is_trivially_destructible<T>、追加のストレージを有効にする必要があります。std::vector<bool>size()

基本的に、T簡単に破壊できる場合は、ユーザーが要求したときに初期化するだけで、何も破壊する必要はありません。それ以外の場合、物事はもう少しトリッキーで、どの要素が構築され、どの要素が初期化されていないかを追跡して、必要なものだけを破棄する必要があります。

  • アップサイジングまたはコンテナの作成:
    1. !std::is_trivially_destructible<T>それに応じてストレージのサイズを変更する場合
    2. メモリ割り当て
    3. ユーザーの要求に応じたオプションの初期化:
      • no_init=> の場合!std::is_trivially_destructible<T>、要素に初期化されていないフラグを立てます。そうでなければ何もしません。
      • (Args...)=>std::is_constructible<T, class... Args>各要素に対してそのコンストラクターを呼び出す場合。の場合!std::is_trivially_destructible<T>、要素に構築済みのフラグを立てます。
  • 小型化またはコンテナの破壊:
    1. オプションの破壊:
      • std::is_trivially_destructible<T>何もしない場合
      • そうでなければ、各要素に対して、構築済みとしてフラグが立てられている場合は、そのデストラクタを呼び出します
    2. メモリ解放
    3. !std::is_trivially_destructible<T>それに応じてストレージのサイズを変更する場合

パフォーマンスの観点からは、T簡単に破壊できる場合は素晴らしいことです。デストラクタがある場合、物事はより制約されます。コンストラクタ/デストラクタの呼び出しがいくつか得られますが、追加のフラグ ストレージを維持する必要があります。最終的には、コンストラクタ/デストラクタが十分に複雑かどうかによって異なります。

また、コメントで提案されているように、に基づいて連想配列を使用しstd::unordered_map、フィールドを追加しsize_t vector_size、実装resizeしてオーバーライドすることもできますsize。そうすれば、初期化されていない要素は保存されません。一方、インデックス作成は遅くなります。

于 2014-02-27T11:07:10.470 に答える