C#から見たC++の例外処理仕様の違い

普段C#を使っていて、C++を使った時に例外を使用した時に感じた違いを書いていきたいと思います。

まずは、言語使用上、違う部分から行きたいと思います。(ちなみに動作をVS2017のVC++で確認してるので規格と違うことがあるかもしれません。

その1:C++の例外は必ずしも特定の基底クラスを持たない

C#は例外は全てSystem.Exceptionをを継承していないといけないという言語上のルールがありましたが、C++にはそういった決まりはありません。throwで投げられる例外の型は任意の型を自由にthrow出来ます。

// 以下の書き方は全部OK

throw 1;
// → catch (int ex) で受けられる

throw "ERROR!!";
// → catch (char * ex) で受けられる

throw MyClass(1.5f);
// → catch (Myclass ex) で受けられる

その2:finallyは存在しない

なんと(?)finallyは存在しません。デストラクタ使えばいいでしょというのが見解だそうです。なので適切にリソースを解放するようにしましょう。

その3:全てcatchの書き方が微妙に違う

C#では、よく、やってはいけないとよく言われるExceptionをキャッチするケースをC++でもやろうとするとこんな感じになります。

catch(...)
{
    // any
}

C#のcatch(Excetpion)に相当するコードはC++だとこのようになるのですが、この場合例外を変数で受ける取る事ができません。オブジェクトが共通基底クラスを持たないためしょうがないんですかね?

C#的な使い方(1):自作例外は全てstd::exceptionを継承する

本当にこれだけは守りましょう。守らないと大変な状態になります。このとき継承したクラス側に特に処理が無ければ空でも最低限の動作はします。

class MyException : public std::exception
{
    // nop
}

C#的な使い方(2):catch節で型によって処理を分岐する

以下コード例のようにC#で例外を一旦Exceptionで受けて特定の例外の時だけ何か処理をする場合が有ったりします。

// C#
catch(Exception ex)
{
    if(ex.GetType() == typeof(MyException))
    {
        // recovery
    }
    else
        throw;
}

これと同等のコードを書きたいときはtypeidを使って以下のように記述すると判断ができうるようになります。

// C++
#include <typeinfo>

catch (std::exception& ex) // &を付けて受け取る
{
    if (typeid(MyException) == typeid(ex))
    {
        // any
    }
    else
        throw;
}

その他の仕様(1):例外が発生することを明示する

【C++11から非推奨】例外を投げる、投げ内を明示する書き方。ずっと前からある。Javaの例外指定が人類にやや早すぎたのと同じる理由でこの構文も人類に"正しく"扱えないと思われます。

void foo() throw(); // 例外を投げない

// MyExceptionのみ投げる
// それ以外がthrowされるとstd::unexpectedの関数が呼ばれるらしい
// らしいというのは、VCだと既定の呼び出しのstd::terminateが呼ばれない感じ?
void foo() throw(MyException);

C++11の新しい構文は、例外が起きるか起きないかを指定するのに新しい式が追加されています。

void foo() noexcept; // noexcept(true) でもおなじ、例外を投げない
void foo() noexcept(false); // 例外を投げる

noexept指定で例外が操作の外に投げられた場合は、以std::terminateになるそうです。(というのもこの動作は実際には何故か見たことが無いですね・・・

使い方は「基本的に全ての操作で例外が発生する」ことを前提に「例外が起きないこと」だけを明示するような使い方になると思います。C#と同じですね。

参考資料

本の虫:C++の例外指定

Qiita:自作クラスをムーブする

EZ-NET:C++ で例外を使う

さいごに

メモリ操作があるので、メモリリークの危険性(リスク)を認識して管理する必要が別途あるので注意が必要かと思います。また、C++は、もろもろ違反するとstd::terminateなので割り切って考えやすいとは思います。