N2855 noexceptについて


C++0xの新しい提案でN2855(Rvalue References and Exception Safety)では、noexceptというキーワードが追加されています。
まずはコードから見ます。

noexcept int foo(int);
int bar(int);
noexcept void wibble(int x, int y) {
  x = foo(x); // OK: foo()は例外を投げない
  y = bar(y); // ERROR: bar()は例外を投げることが出来る

  try {
    y = bar(y); 
  } catch (...) {
    y = 0;
  } // OK: 全ての例外はすでに捕捉されている。
}

このように、noexceptで修飾された関数は、例外を投げない事を静的に保障するための機能です。


なぜこのような提案が出たのかと言うと、moveコンストラクタが例外を投げると困ると言う所から来ています。
moveコンストラクタが例外を投げた場合に不味い事をstd::vectorのpush_back()を例に上げています。
std::vector::push_back(x)で保存領域を拡張してxをコンテナに追加する場合の例です。

T* reallocate(T *old_ptr, size_t old_capacity) {
  // #1: 新しいストレージを確保
  T* new_ptr = (T*)new char[sizeof(T) * old_capacity * 2];

  // #2: 新しいストレージに要素を移すことを試します
  unsigned i = 0;
  try {
    // #2a: 古い要素を右辺値としてあつかい古いストレージから対応する要素を新しいストレージに初期化します。 
    for (; i < old_capacity; ++i)
      new (new_ptr + i) T(std::move(old_ptr[i])); // "move" operation
  } catch (...) {
    // #2b: 新しいストレージに作ったコピーを破棄します
    for (unsigned v = 0; v < i; ++v)
      new_ptr[v]->~T();
    delete[]((char*)new_ptr);
    throw;
  }

  // #3: 古いストレージを開放します。
  for (i = 0; i < old_capacity; ++i)
    old_ptr[i]->~T();
  delete[]((char*)old_ptr);
  return new_ptr;
}

Tがコピーコンストラクタを持ちmoveコンストラクタを持たないクラスの場合、要素の移動は以下のように、
上記コード内の#2aでコピーコンストラクタによって行われます。

コピー元
abcd efgh

コピー先
abcd                           

Tがmoveコンストラクタを持ったクラスの要素の移動は#2aでは以下のように元の要素を破壊して移動してしまうため、
コンテナの状態を例外を送出する前に復元することが出来なくなります。
コピー元
???? efgh

コピー先
abcd                           
これでは不味いので、moveコンストラクタのように例外を投げてはいけない関数に対して、例外安全をコンパイル時に保障しようというのがnoexceptです。


noexceptには続きがあるのですが、続きは別な日に書きます。


※最新のドラフトN2914にはまだ追加されてないので、採用が決まったのか知っている方が居ましたら教えてください。
補足:
コード中の#2aのT(std::move(old_ptr[i]))がコピーコンストラクタを呼び出すのはreference collapsionが適用されるため。