C++の自明なメソッドが暗黙に定義されるか場合分け

(2017年6月) 新規作成。

C++ では、互換性の観点と開発の便宜のため, 暗黙にコンストラクタ (構築子)、代入演算子、デストラクタ (破壊子) が定義される。自明 (trivial) な特殊メンバ関数と呼ばれる。

開発者が陽に (明示的に) これらのメンバ関数を定義した場合、自明なメソッドは定義されない。が、ややこしいことに, 単に対応するメンバ以外にも削除されるものがある。

表としてまとまったものが見当たらなかったので、作ってみた。

特殊メンバ関数とコンパイラによる暗黙宣言 - yohhoyの日記 や, コンストラクタが暗黙に宣言されるとき、されないとき があったが、残念なことに、2017年6月現在, 誤りが含まれている。

下の表は、一番左の列のメンバを陽に定義した場合に、2列目以降のメンバが自明か削除されるかを示す。trivial は自明なメンバが暗黙に定義され、deleted は削除される。

削除されても, = default で自明なメンバを復活できる。

表中の noexcept は例外を発生しない。false は潜在的に例外を発生しうる noexcept(false) を表す.

Fedora 25 Linux の gcc 6.3.1 を用いた。デフォルトで C++14 (C++11と変わらない). また, c++1z でも変わらない。

下のメンバを定義したときに, 右のがどうなるか default ctor copy ctor move ctor copy assign move assign dtor
なし trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept
default ctor trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept
default ctor noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept
copy ctor deleted trivial false trivial noexcept trivial noexcept trivial noexcept
copy ctor noexcept deleted trivial noexcept trivial noexcept trivial noexcept trivial noexcept
move ctor deleted deleted deleted deleted trivial noexcept
move ctor noexcept deleted deleted deleted deleted trivial noexcept
copy assign trivial noexcept trivial noexcept trivial noexcept trivial false trivial noexcept
copy assign noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept
move assign trivial noexcept deleted deleted deleted trivial noexcept
move assign noexcept trivial noexcept deleted deleted deleted trivial noexcept
dtor trivial noexcept trivial noexcept trivial noexcept trivial noexcept trivial noexcept
dtor noexcept(false) trivial false trivial false trivial false trivial noexcept trivial noexcept

整理すると、次のようになる。

  1. 何らかのコンストラクタを定義すると, 自明なデフォルトコンストラクタが削除
  2. ムーブコンストラクタ・代入演算子を定義すると、コピーコンストラクタ・代入演算子、ムーブコンストラクタ・代入演算子が削除
  3. コピーコンストラクタ・代入演算子が例外を発生しうる場合、対応する、ムーブコンストラクタまたはムーブ代入演算子も, trivial だが, 例外を発生しうる。
  4. デストラクタが例外を発生しうる場合, コンストラクタすべてが、trivial たが例外を発生しうる

簡単なテストコード

コメントアウトしたりして、試してみる。

C++
[RAW]
  1. #include <utility>
  2. #include <iostream>
  3. using namespace std;
  4. struct S {
  5. // デフォルトコンストラクタ (構築子)
  6. S() noexcept { }
  7. // コピー構築子
  8. // つられてムーブコンストラクタも noexcept(false) になる!
  9. S(const S& x) { }
  10. // ムーブ構築子だけ定義すると, コピー構築子は deleted になる.
  11. S(S&& x) noexcept(false) = default; //{ }
  12. // コピー代入演算子
  13. S& operator=(const S& x) { return *this; }
  14. // ムーブ代入演算子
  15. S& operator=(S&& x) { return *this; }
  16. // デストラクタ
  17. ~S() { }
  18. };
  19. int main() {
  20. S s;
  21. cout << noexcept(S()) << "\n";
  22. cout << "copy ctor " << is_nothrow_copy_constructible<S>::value << "\n";
  23. cout << "move ctor " << is_nothrow_move_constructible<S>::value << "\n";
  24. cout << is_nothrow_copy_assignable<S>::value << "\n";
  25. cout << is_nothrow_move_assignable<S>::value << "\n";
  26. cout << noexcept(s.~S()) << "\n";
  27. return 0;
  28. }

コピー構築子を noexcept(false) で定義すると、ムーブ構築子もそうなる、って一体何なんだ?

デフォルト構築子、コピー構築子、コピー代入演算子は、過去との互換性の確保から、noexcept を強制できない。でも、ムーブ構築子、ムーブ代入演算子は、noexcept を暗黙に指定してもよかったんじゃないかなぁ?