GotW#8のタスクは、テンプレート引数のデストラクタのみがスローしないことを前提として、例外に依存しない汎用スタックデータ構造をC++で実装することです。秘訣は、スローされる可能性のあるテンプレート引数操作(コンストラクター、コピーコンストラクター、代入)を処理して、スタックがスローされた場合に一貫した状態のままにすることです。
解決策では、ハーブサッターは言います
このソリューションを単純にするために、例外セーフなリソース所有権の基本クラスの手法を示さないことにしました。
少しグーグルした後、1997年にさかのぼるDave Abrahamsによるこの答えを見つけました。彼のソリューションでは、彼は基本クラスのメモリの割り当てと削除を処理し、サブクラスのスタック操作を実装しています。このようにして、コピーコンストラクタでの要素のコピーがメモリ割り当てから分離されていることを確認します。これにより、コピーが失敗した場合は、何があっても基本クラスのデストラクタが呼び出されます。
参考までに、コメントを追加したDaveのコピーコンストラクターを次に示します。
// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
: StackBase<T>( rhs.Count() ) // constructor allocates enough space
// destructor calls delete[] appropriately
{
while ( Count() < rhs.Count() )
Push( rhs.v_[ Count() ] ); // may throw
}
基本コンストラクタが成功すると、サブクラスのコピーコンストラクタがスローした場合でも、基本デストラクタでのメモリのクリーンアップが保証されます。
私の質問は次のとおりです。
- 上で概説した場合を除いて、このアプローチに他の利点はありますか?
自分で問題を解決したときに、このコピーコンストラクターを思いつきました。
// v_ refers to the internal array storing the stack elements // vsize_ is the amount of space allocated in v_ // vused_ is the amount of space used so far in v_ Stack (const Stack &rhs) : vsize_ (0), vused_ (0), v_ (0) { Stack temp (rhs.vused_); // constructor calls `new T[num_elements]` // destructor calls `delete[] v_` std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw swap (temp); } void swap (Stack &rhs) { std::swap (v_, rhs.v_); std::swap (vused_, rhs.vused_); std::swap (vsize_, rhs.vsize_); }
このアプローチと比較すると、基本クラスを持つのはやや面倒です。このtemp-copy-then-swapアプローチよりも基本クラスの手法を優先する必要がある理由はありますか?で使用しているため、Daveと私は両方ともすでに
swap()
メンバーを持っていることに注意してくださいoperator=()
。- Dave Abrahamsのテクニックはあまりよく知られていないようです(Googleによると)。それは別の名前を持っていますか、それは標準的な習慣ですか、私は何かを逃しましたか?
ノート:
- デイブ
Push()
がループ内にいると、私の使用法と同等であると想定します。std::copy
- スマートポインタを使用すると、この演習でメモリを明示的に管理する必要がなくなるため、スマートポインタを回答から除外しましょう。