最終更新日時(UTC):
が更新

履歴 編集

noexcept(C++11)

概要

C++11で導入されたnoexceptキーワードには、以下の2つの意味がある:

ひとつは、throwキーワードによる例外仕様の代替。関数がどの例外を送出する可能性があるかを列挙するのではなく、例外を送出する可能性があるかないかのみを指定する。例外を送出する可能性がある関数にはnoexcept(false)を指定し、例外を送出する可能性がない関数にはnoexcept(true)もしくはnoexceptを指定する:

class Integer {
  int value_ = 0;

public:
  // getValue()メンバ関数は、例外を送出しない
  int getValue() const noexcept
  {
    return value_;
  }
};

noexceptキーワードのもうひとつの意味は、式が例外を送出する可能性があるかどうかを判定する演算子である。noexcept(f(arg))のようにnoexcept演算子に式を指定することで、その式が例外を送出する可能性があるかどうかを、コンパイル時定数のbool値として取得できる。つまり、関数に対して指定されたnoexceptの情報を取得する:

Integer x;
static_assert(noexcept(x.getValue()), "getValue() function never throw exception");

noexceptは、代表的には以下の2つの用途で使用できる:

  1. パフォーマンス向上
    • 例外を送出しないという保証があることで、コンパイラは例外送出によるスタック巻き戻しのためのスタックを確保する必要がなくなる
  2. 例外を決して送出しない強い例外安全性の保証(No-throw guarantee)
    • 例外安全性で有名な問題としてstackpop操作がある。要素型Tのコピーコンストラクタが例外を送出する可能性があるためにpopの関数はTを返すのではなく戻り値型voidとする必要があった。しかしreturn文に指定する式が決して例外を送出しないという保証があることで、popの関数はT型のオブジェクトを返せるようになる。
    • 参照: ジェネリックコンポーネントにおける例外安全性 - boostjp

仕様

例外仕様としてのnoexcept

  • 例外仕様としてのnoexceptには、整数定数式を引数として指定できる。整数定数式は、boolに変換可能であること。
  • noexcept例外仕様に対してfalseに評価される整数定数式を指定した関数は、あらゆる例外を送出する可能性がある。
  • noexcept例外仕様に対してtrueに評価される整数定数式を指定した関数、もしくは引数なしでnoexceptを指定した関数は、いかなる例外も送出してはならない。
  • noexcept例外仕様を指定しない関数は、一部の例外を除いて、noexcept(false)を意味する。
    • デストラクタとdelete演算子は、明示的にnoexcept(falseに評価される整数定数式)を指定しない限り、デフォルトでnoexceptである。

struct X {
  ~X(); // デストラクタはデフォルトでnoexcept(true)

  // 例外を送出する可能性がある
  // ※ std::vectorのコピーコンストラクタは例外を送出する
  std::vector<T> getVector() const;
//std::vector<T> getVector() const noexcept(false);

  // 例外を送出しない
  int getValue() const noexcept;
//int getValue() const noexcept(true);

  // noexceptは参照修飾と後置戻り値型の間
  auto getString() const & noexcept -> std::string;
};

  • noexceptもしくはnoexcept(trueに評価される整数定数式)が指定された関数が例外を送出した場合、std::terminate()関数を呼び出してプログラムを異常終了させる。その際、std::terminate()関数が呼び出される前に、スタックの巻き戻しは起こらない可能性がある。
  • 従来のthrowキーワードによる例外仕様(C++03ではexception specification、C++11ではdynamic exception specificationと呼ばれる仕様)は、C++11以降で非推奨である。
  • noexceptの指定可能な位置は、参照修飾の後、戻り値の型を後置する関数宣言構文の前である。

式が例外を送出する可能性があるか判定するnoexcept演算子

  • 演算子としてのnoexceptは、引数として指定した定数式が例外を送出する可能性があるかどうかをコンパイル時に判定し、bool型の定数値を返す

struct X {
  int f() const noexcept; // noexcept例外仕様

  // 外側はnoexcept例外仕様、内側はnoexcept演算子。
  // メンバ関数関数f()が例外を送出しない場合、関数g()もまた例外を送出しない
  int g() const noexcept(noexcept(f()))
  { return f(); }
};

X x;
// X::f()メンバ関数が例外を送出しない場合、
// isNoexprFはtrue、そうでなければfalseとなる
constexpr bool isNoexprF = noexcept(x.f());

  • この演算子はsizeofdecltypeと同じく、引数として指定された式は、実行時には評価されない
    • 上記コードの場合、x.f()は実行時には呼び出されない
  • noexcept演算子は、以下の状況でfalseを返す:
    • noexcept(false)が指定されているもしくはnoexceptが指定されていない関数、メンバ関数、関数ポインタ、メンバ関数ポインタの呼び出し。(例として、new式からの確保関数の呼び出しといった、暗黙の呼び出し)
    • throw
    • 実行時型チェックが行われる式として、参照型を引数とするdynamic_cast式の呼び出し、および多態的に振る舞う型の左辺値に対するtypeid式の呼び出し

#include <iostream>
#include <stack>
#include <deque>
#include <type_traits>

template <class T, class Container = std::deque<T>>
class movable_stack : public std::stack<T, Container> {
  using base = std::stack<T, Container>;

  static_assert(std::is_nothrow_default_constructible<T>{},
                "T must be nothrow default constructible");
  static_assert(std::is_nothrow_move_constructible<T>{},
                "T must be nothrow move constructible");

public:
  // クラスのテンプレートパラメータTに対して、
  // ムーブコンストラクタが例外を送出しないことを要求しているので、
  // pop操作は例外を送出することなくreturnで要素を返せる。
  std::pair<T, bool> move_pop() noexcept
  {
    if (base::empty()) {
      return std::make_pair(T(), false);
    }

    T x = std::move(base::top());
    base::pop();
    return std::make_pair(std::move(x), true);
  }
};

int main()
{
  movable_stack<int> s;
  s.push(1);
  s.push(2);
  s.push(3);

  while (!s.empty()) {
    int next_value = s.move_pop().first;
    std::cout << next_value << std::endl;
  }
}

出力

3
2
1

この機能が必要になった背景・経緯

noexcept機能は、ムーブコンストラクタから例外を送出することを許可する際に提案された。

ムーブ操作は基本的には例外を送出しない。そのため、例外を送出しないという、例外安全性の強い保証がしやすい仕組みと言える。ムーブに例外を送出しない保証があれば、より最適化された実装を選択できるだろう。しかし、ムーブ操作が例外を送出する可能性があるのであれば、例外を送出しないムーブ操作のための最適化された実装とそれ以外を呼び分ける仕組みが必要となる。

そういった例外を送出しない判定や指定は、従来のthrowキーワードによる例外仕様の範囲を超えていた。そのために、noexceptという機能が新設され、その機能で必要十分となったために従来の例外仕様は非推奨となった。

関連項目

参照