例外を投げない move constructor には noexcept
を付ける必要があることを書いた記事はいくつか見つかります.
例えば「C++ - ムーブコンストラクタを作って push_back してみる - Qiita」では,
std::vector の push_back 時にコピーコンストラクタが呼ばれてしまう原因は、自作クラスのムーブコンストラクタに noexcept 修飾子を付けていなかったことが原因だったようだ。
のように move constructor に noexcept
を付ける必要があることが示唆されています.
また std::vector<>
がバッファの伸長時に std::move_if_noexcept()
を使っているということも書かれています.
それではなぜ std::move_if_noexcept()
を使うのでしょうか.std::move
ではなぜだめなのでしょうか.
答え
ひとことで言うと例外安全性について強い保障をするためです.
std::vector<>::emplace_back
や push_back
のようなメンバ関数の強い例外安全性を保障するために,例外を投げる可能性のある move constructor を使うことを避けています.それを実現しているのが std::move_if_noexcept
ということになります.
std::vector<>::empalce_back
などで要素の追加をするとき,バッファが不足していると新たにバッファを確保し新しいバッファにもともとあった要素を copy construct または move construct します.このときに std::move_if_noexcept
が使われます.
さて std::move_if_noexcept
ではなく std::move
を使い move constructor の中で例外が発生した場合どうなるかを考えてみます.
void foo(std::vector<A>& vec) {
vec.empalce_back(); // 1
}
上のコードの 1 の部分で A
の move constructor が例外を投げると vec
は不定な状態になります.いくつかの要素を move したあとでは,それらを確実にもとの状態に戻す方法が存在しないためです.確実にもとの状態に戻すには例外が投げられないことを保障する必要があります.
一方 move constructor の代わりに copy constructor を使っていれば元のバッファに戻すだけで強い例外安全を保障することができます.
例外を投げない move constructor の場合は,そもそも例外が投げられないので新しいバッファへ確実に移動することができます.
まとめ
-
std::vector<>
はバッファの伸長時にnoexcept
でない move constructor は使用しません. -
noexcept
がない move constructor は強い例外安全を保障するためには使えないことがあります. - 例外を投げない move constructor には
noexcept
を付けることで最適化の可能性が高まります.
参考
-
noexcept specifier (since C++11) - cppreference.com
- Notes にこの記事で書いたようなことが言及されています.