17

オペレーター (PODのnew場合は malloc/calloc) は、大量のメモリ チャンクを割り当てるときに、シンプルで効率的な形式の失敗をサポートします。

これがあるとしましょう:

const size_t sz = GetPotentiallyLargeBufferSize(); // 1M - 1000M
T* p = new (nothrow) T[sz];
if(!p) {
  return sorry_not_enough_mem_would_you_like_to_try_again;
}
...

std::containers にそのような構造はありますか、それとも友達と (予想される!!) 例外を常に処理する必要がstd::vectorありますか?


メモリを事前に割り当てるカスタム アロケータを作成し、このカスタム アロケータをベクトルに渡す方法があるのではないでしょうか。そのため、事前にアロケータに入れるよりも多くのメモリをベクトルが要求しない限り、失敗しません。 ?


後付け: 本当に必要なのはbool std::vector::reserve(std::nothrow) {...}、通常の予約関数に加えてメンバー関数です。しかし、アロケーターが拡張されて nothrow 割り当てが可能になった場合にのみ意味があるため、それは起こりません。結局、(nothrow)新しいものは何かに適しているようです:-)


編集:なぜ私がこれを求めているのかについて:

デバッグ中にこの質問について考えました (デバッガーの 1 回目のチャンス / 2 回目のチャンスの例外処理):また、すでに十分に予測され、コードで処理されている bad_alloc 例外もキャッチしました。それはそれほど大きな問題ではありませんでしたが、例外は例外的な状況のためのものであり、コード内のすべての奇妙な呼び出しが例外的ではないことをすでに期待しているという説教が行われていることに気づきました。

正当な用途がある場合new (nothrow)は、 vector-notrow-reserve にもあります。

4

3 に答える 3

16

デフォルトでは、標準の STL コンテナー クラスは内部でクラスを使用std::allocatorして割り当てを行います。これが、std::bad_alloc使用可能なメモリがない場合にスローできる理由です。興味深いことに、アロケーターに関する C++ ISO 仕様では、アロケーター型の戻り値は、いくつかの要素を保持できるメモリ ブロックへのポインターでなければならないことが規定されていますnothrownewこのようなサイレント割り当ての失敗が発生する可能性があります。ただし、使用可能なメモリがない場合にプログラムを終了するカスタム アロケータを作成することはできます。これは、メモリが残っていない場合に返されたメモリが有効であることは空虚な真実だからです。:-)

つまり、標準コンテナーはデフォルトで例外をスローします。例外がスローされないようにカスタム アロケーターを使用してコンテナーをカスタマイズしようとしても、仕様に準拠しません。

于 2011-01-28T10:04:23.567 に答える
6

「効率が悪いから例外は使いたくない」という声をよく耳にします。

すべての実行時の型情報をオフにする「組み込み」環境について言及している場合を除き、例外が適切な方法でスローされている場合、例外の非効率性についてあまり心配する必要はありません。メモリ不足は、これらの適切な方法の 1 つです。

vector のコントラクトの一部は、割り当てられない場合にスローされることです。代わりに NULL を返すカスタム アロケータを作成すると、未定義の動作が発生するため、さらに悪化します。

次に、アロケーターを使用する必要がある場合、アロケーターは、使用可能な場合は最初に失敗した割り当てコールバックを試行し、それからまだスローに割り当てることができない場合にのみ、例外が発生する必要があります。

ただし、ヒントを教えてください。実際にこのような大量のデータを割り当てている場合、 vector はおそらく間違ったクラスであり、代わりに std::deque を使用する必要があります。なんで?deque はメモリの連続ブロックを必要としないため、一定時間のルックアップであるためです。そして、利点は 2 つあります。

    • 割り当てが失敗する頻度が低くなります。連続したブロックは必要ないため、単一のブロックではありませんが、メモリを利用できる可能性があります。
    • 再割り当てはなく、割り当てが増えるだけです。すべてのオブジェクトを移動する必要があるため、再割り当てにはコストがかかります。大音量モードの場合は、非常にタイムリーな操作になります。

過去にそのようなシステムに取り組んだとき、上記の理由 1 により、ベクトルを使用した場合よりも deque を使用した場合の方が実際には 4 倍以上のデータを保存でき、理由 2 により高速であることがわかりました。

他に行ったことは、2MB の予備バッファーを割り当て、bad_alloc をキャッチしたときにバッファーを解放し、とにかくスローして、容量に達したことを示しました。しかし、2MB のスペアがあれば、メモリから一時的なディスク ストレージにデータを移動するための小さな操作を実行するためのメモリがあることは少なくともわかっていました。

したがって、メモリ不足は常に致命的であり、プログラムを終了する (またはさらに悪いことに、呼び出し未定義の動作)。

于 2011-01-28T10:58:26.187 に答える
3

標準コンテナはこれに例外を使用します。成功することがわかっている場合にのみ割り当てを試行する以外に回避することはできません。実装は通常、不特定の量だけ過剰に割り当てられるため、移植可能に行うことはできません。コンパイラで例外を無効にする必要がある場合、コンテナーでできることは限られています。

std「シンプルで効率的」に関しては、コンテナーは合理的にシンプルで合理的に効率的だと思います。

T* p = new (nothrow) T[sz];
if(!p) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}
... more code that doesn't throw ...
delete[] p;

try {
    std::vector<T> p(sz);
    ... more code that doesn't throw ...
} catch (std::bad_alloc) {
    return sorry_not_enough_mem_would_you_like_to_try_again;
}

コードの行数と同じです。失敗した場合に効率の問題が発生する場合、プログラムは毎秒数十万回失敗しているに違いありません。この場合、プログラムの設計に少し疑問があります。newまた、例外をスローしてキャッチするコストが、要求を満たすことができないことを確立するためにおそらく行われるシステム コールのコストと比較して、どのような状況で重要なのか疑問に思います。

しかし、さらに良いことに、例外も使用するように API を作成するのはどうでしょうか。

std::vector<T> p(sz);
... more code that doesn't throw ...

元のコードよりも 4 行短くなり、現在 "sorry_not_enough_mem_would_you_like_to_try_again" を処理する必要がある呼び出し元は、代わりに例外を処理できます。このエラー コードが呼び出し元の複数の層を通過する場合、各レベルで 4 行を節約できます。C++ には例外があり、ほとんどすべての目的で、これを受け入れてそれに応じてコードを書くこともできます。

「(expected!!)」について - エラー状態の処理方法を知っている場合があります。その場合にすべきことは、例外をキャッチすることです。それが例外の仕組みです。例外をスローするコードが、例外をキャッチする意味がないことをどういうわけか知っている場合、代わりにプログラムを終了させることができます。

于 2011-01-28T11:12:29.697 に答える