はじめに
C++の関数は、潜在的に例外を投げる可能性があります。しかし、ゲッター関数やムーブコンストラクタのように、「この関数は絶対に例外を送出しない」と分かっているものも多くあります。
C++11で導入された**noexcept
指定子は、関数宣言の後ろに付けることで、その関数が例外を投げない**ことをコンパイラに伝えます。これにより、コンパイラはより積極的な最適化を行えるようになり、パフォーマンスが向上する可能性があります。
また、noexcept
はプログラマにとっても、関数の仕様を明確にするための重要なドキュメントとしての役割を果たします。
【前提】C++11とは?
C++11は、2011年に正式化されたC++言語のメジャーアップデート版です。noexcept
はこのC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。
noexcept
の使い方
1. noexcept
: 無条件に例外を送出しない
noexcept
(またはnoexcept(true)
)を付けると、その関数は例外を送出しないとマークされます。もし、noexcept
が指定された関数から例外が送出されてしまうと、プログラムは即座にstd::terminate
を呼び出して異常終了します。
サンプルコード
#include <iostream>
#include <string>
#include <utility> // move
using namespace std;
class Widget {
private:
string name_;
public:
// このゲッター関数は、例外を投げる処理を含まない
const string& getName() const noexcept {
return name_;
}
// ムーブコンストラクタやムーブ代入演算子は、
// noexceptにしておくと、STLコンテナなどがより効率的に扱ってくれる
Widget(Widget&& other) noexcept {
name_ = move(other.name_);
}
};
経験則: デストラクタ、ムーブコンストラクタ/ムーブ代入演算子、swap
関数など、例外を投げるべきではないと一般的に考えられている関数には、積極的にnoexcept
を付けるべきです。
2. noexcept(条件式)
: 条件付きで例外を送出しない
noexcept
のカッコの中に、コンパイル時に評価される条件式を入れることで、条件に応じてnoexcept
指定を有効にするか決めることができます。
これは、特にテンプレートプログラミングで、テンプレート引数の型が持つ関数のnoexcept
性を引き継ぎたい場合に非常に強力です。
サンプルコード
class Resource {
public:
void process() const { /* 例外を投げる可能性あり */ }
};
class SafeResource {
public:
void process() const noexcept { /* 例外を投げない */ }
};
// テンプレート関数
template <typename T>
void execute_process(const T& item) noexcept(noexcept(item.process())) {
item.process();
}
int main() {
// is_nothrow_invocable_v はC++17の機能
cout << boolalpha;
cout << "execute_process<Resource> はnoexcept? -> "
<< noexcept(execute_process(Resource{})) << endl; // -> false
cout << "execute_process<SafeResource> はnoexcept? -> "
<< noexcept(execute_process(SafeResource{})) << endl; // -> true
return 0;
}
解説:
noexcept(item.process())
:noexcept
演算子は、引数に渡された式のnoexcept
性をbool
値で返します。item
がResource
型の場合:item.process()
はnoexcept
ではないので、false
を返します。item
がSafeResource
型の場合:item.process()
はnoexcept
なので、true
を返します。
void execute_process(...) noexcept(...)
:execute_process
関数自身のnoexcept
性が、内部で呼び出すitem.process()
のnoexcept
性と連動するようになります。
まとめ
今回は、C++11のnoexcept
指定子について解説しました。
noexcept
: 関数が例外を送出しないことを明示する。コンパイラの最適化を助け、パフォーマンス向上に繋がる可能性がある。- デストラクタやムーブ操作には、原則として
noexcept
を付けるべき。 noexcept(条件)
: 条件付きでnoexcept
を適用し、テンプレートなどで**noexcept
性を伝播**させる。
noexcept
を適切に使うことで、コンパイラと、コードを読む他のプログラマの両方に対して、関数の挙動に関する重要な情報を提供し、より安全で効率的なコードを書くことができます。