2014-10-09

C++の例外指定

C++では、関数に例外指定というものが記述できる。これはC++98からある機能で、throw(T1, T2, ...)という文法で、関数が外に投げる例外を指定する機能だ。

// C++98/03
void f() throw( int, double ) ;

もし、関数が例外指定に指定された以外の型を投げた場合、std::unexpectedが呼ばれる。

void f() throw( int ) 
{
    throw 0 ; // OK
    throw 0.0 ; // 実行時エラー: call std::unexpected() 
}

例外指定は、関数から抜けだせる例外を指定する機能で、関数の外に例外を投げなければ問題がない。

void f() throw( int ) 
{
    try {
        throw 0.0 ; // OK
    } catch( ... ) { } // OK、外に投げない
}

というのは、C++11以前の歴史の話だ。

現実に、この例外指定を真面目に実装するコンパイラーは少なかった。パースだけして無視するような有名なコンパイラーがはびこったのだ。結局、実装されないのであれば机上の空論でしかない。C++11では、これを動的例外指定と呼び、deprecated扱いしている。規格が現実に追いついたというわけだ。

ところで、この例外指定の文法は、本来規格が想定していなかった意味で使われ始めた。動的例外指定は、文法上、型を指定しないこともできる。

void f() throw( ) ;

ある主要なC++コンパイラーは、この記述を、関数は例外を外に投げないという意味にみなし、最適化のためのヒントに使い始めた。これは、本来規格設計時に意図された意味ではなかったが、最適化のヒントのため、広まった。世の中のC++コンパイラーは、動的例外指定を完全に実装しなかったが、これは実装するだけの価値があったのだ。

C++11では、無例外保証の指定機能だけに特化した、noexceptが追加された。noexceptを指定すると、関数は外に例外を投げないと指定したことになる。

void f() noexcept ;

もし外に例外を投げると、std::terminateが呼ばれる。

また、noexceptにはbool型のコンパイル時定数のオペランドを取る文法がある。式がtrueの時は無例外指定になり、falseの時は無例外指定にならないという意味を持つ。

void f() noexcept(true) ; // 例外を外に投げない
void g() noexcept(false) ; // 例外を外に投げる可能性がある

これにより、コンパイル時メタプログラミングにより、関数の無例外指定の有無を切り替えられるようになった。

無例外指定のない通常の関数宣言は、例外を外に投げる可能性がある関数である。throw()も、無例外指定とみなされる。

void f1() ;
void f2() throw() ; // 無例外指定
void f3() noexcept ; // 無例外指定
void f4() noexcept( true ) ; // 無例外指定
void f5() noexcept( false ) ;

また、C++11ではnoexcept式というものが追加されている。これは、オペランドの式が例外を投げるかどうかをboolで返す。オペランドの式は未評価式である。

void f() ;
void g() noexcept ;

int main()
{
    noexcept( f() ) ; // false
    noexcept( g() ) ; // true
}

ドワンゴ広告

この記事はドワンゴ勤務中に書いた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

No comments: