TENKAIKEN Storehouse

Contents ♪

C++ 例外処理 2024.08.11
try例外送出・catchで捕捉
throw文で任意のオブジェクトを例外として送出する。
tryブロック内で例外が送出されたとき、それ以降の処理に進まずcatchブロックに処理がジャンプする。例外オブジェクトは一般にstd:exceptionから派生したサブクラスとして定義するが、その限りではなく整数型などでもよい。
#include <iostream>
using namespace std;

int main()
{
    try {
        cout << "try start" << endl;
        throw -1;
        cout << "try end" << endl;
    } catch (int e) {
        cout << "exception int=" << e << endl;
    }
    return 0;
}
try start
exception int=-1
C++ 例外処理 2024.08.11
文字列の例外オブジェクト
文字列定数を例外オブジェクトとして送出すると、文字列ポインタで例外のメッセージを得ることができる。
#include <iostream>
using namespace std;

int main()
{
    try {
        cout << "try start" << endl;
        throw "this is a exception";
        cout << "try end" << endl;
    } catch (const char* e) {
        cout << "exception " << e << endl;
    }
    return 0;
}
try start
exception this is a exception
C++ 例外処理 2024.08.11
関数からの例外送出
tryブロック内で呼び出す関数から例外が送出されると、その関数のそれ以降の処理を中断し即座に戻り、呼び出し元のcatchブロックで捕捉される。
#include <iostream>
using namespace std;

void throwex()
{
    cout << "before throw" << endl;
    throw -1;
    cout << "after throw" << endl;
}

int main()
{
    try {
        cout << "try start" << endl;
        throwex();
        cout << "try end" << endl;
    } catch (int e) {
        cout << "exception=" << e << endl;
    }
    return 0;
}
try start
before throw
exception=-1
C++ 例外処理 2024.08.11
例外スローの関数の伝播
例外を送出した関数を呼び出した関数でtry-catchで捕捉しなかった場合は、更にその呼び出し元へ捕捉されるまで伝播する。
捕捉されないまま伝播していくと、最終的にはmain()に達し、main()でも捕捉されなかった場合は、std:terminate()が呼び出され強制終了となる。
#include <iostream>
using namespace std;

void throwex1()
{
    cout << "before throw" << endl;
    throw -1;
    cout << "after throw" << endl;
}

void throwex2()
{
    cout << "before call throwex1()" << endl;
    throwex1();
    cout << "before call throwex1()" << endl;
}

int main()
{
    cout << "try start" << endl;
    throwex2();
    cout << "try end" << endl;
    return 0;
}
try start
before call throwex1()
before throw
terminate called after throwing an instance of 'int'

Command terminated
C++ 例外処理 2024.08.11
例外の再送出
catchで捕捉した例外オブジェクトをthrowで再送出できる。
捕捉した例外オブジェクトを再送出するとき、そのオブジェクトのコピーが生成される。
#include <iostream>
using namespace std;

void throwex1()
{
    cout << "before throw" << endl;
    throw -1;
    cout << "after throw" << endl;
}

void throwex2()
{
    try {
        cout << "before call throwex1()" << endl;
        throwex1();
        cout << "before call throwex1()" << endl;
    } catch (int e) {
        cout << "exception int=" << e << " rethrow" << endl;
        throw e;
    }
}

int main()
{
    try {
        cout << "try start" << endl;
        throwex2();
        cout << "try end" << endl;
    } catch (int e) {
        cout << "exception int=" << e << endl;
    }
    return 0;
}
try start
before call throwex1()
before throw
exception int=-1 rethrow
exception int=-1
C++ 例外処理 2024.08.11
複数の例外オブジェクトの捕捉
例外の種類はオブジェクト型で判別する。その型ごとに複数のcatchで捕捉できる。
#include <iostream>
using namespace std;

void throwex()
{
    cout << "before throw" << endl;
    throw 10000L;
    cout << "after throw" << endl;
}

int main()
{
    try {
        cout << "try start" << endl;
        throwex();
        cout << "try end" << endl;
    } catch (int e) {
        cout << "exception int=" << e << endl;
    } catch (long e) {
        cout << "exception long=" << e << endl;
    }
    return 0;
}
try start
before throw
exception long=10000
C++ 例外処理 2024.08.11
全ての例外を捕捉するcatch
catch(...)はすべての例外オブジェクトを捕捉する。
ただしこの場合は、例外オブジェクトは取得できない。複数のcatchブロックの末尾にのみ配置できる。
#include <iostream>
using namespace std;

void throwex()
{
    cout << "before throw" << endl;
    throw 10000L;
    cout << "after throw" << endl;
}

int main()
{
    try {
        cout << "try start" << endl;
        throwex();
        cout << "try end" << endl;
    } catch (int e) {
        cout << "exception int=" << e << endl;
    } catch (...) {
        cout << "unknown exception" << endl;
    }
    return 0;
}
try start
before throw
unknown exception
C++ 例外処理 2024.08.11
クラス定義した例外オブジェクト
任意のクラスを例外オブジェクトとして定義する。
コンストラクタで生成したインスタンスをthrow()で例外送出すると、そのオブジェクトはcatchで捕捉される。
通常、クラス定義から生成した例外オブジェクトは、例の(1)のようにcatchで参照で受け取る。
例のMyExcクラスは、コンストラクタ、デストラクタとshow()メンバ関数で自身のインスタンスのポインタ値を表示させている。try start (1)とtry start (2)の2回に分けたtry-catchで、同じようにthrow()でMyExcクラスのオブジェクトを例外送出している。(1)と(2)の違いは、catchのときに例外オブジェクトを参照で受け取るかそうでないかである。
(1)では、throw()時に生成したインスタンスをcatchブロック内で参照し続けているが、(2)では、catchの時点で別のインスタンスが作られていることがわかる。関数渡しと同様なコピーが行われている。
捕捉したオブジェクトは、catchスコープを出るときに消滅する。
#include <iostream>
using namespace std;

class MyExc {
public:
    MyExc() { cout << "MyExc created this=" << (unsigned long)this << endl;}
    ~MyExc() { cout << "MyExc deleted this=" << (unsigned long)this << endl; }
    void show() { cout << "exception MyExc this=" << (unsigned long)this << endl; }
};

int main()
{
    try {
        cout << "try start (1)" << endl;
        throw MyExc();
        cout << "try end" << endl;
    } catch (MyExc& e) {                // 参照で受け取る
        cout << "catch start" << endl;
        e.show();
        cout << "catch end" << endl;
    }

    try {
        cout << "try start (2)" << endl;
        throw MyExc();
        cout << "try end" << endl;
    } catch (MyExc e) {
        cout << "catch start" << endl;
        e.show();
        cout << "catch end" << endl;
    }
    return 0;
}
try start (1)
MyExc created this=94778416456512
catch start
exception MyExc this=94778416456512
catch end
MyExc deleted this=94778416456512
try start (2)
MyExc created this=94778416456512
catch start
exception MyExc this=140721207255807
catch end
MyExc deleted this=140721207255807
MyExc deleted this=94778416456512
C++ 例外処理 2024.08.11
例外オブジェクトの派生型の区別
送出する例外オブジェクトが、あるスーパークラスからの派生したサブクラス型のとき、スーパークラス型で受けるcatchは、そのサブクラスのインスタンスも受け取ることができる(例の(1))。
また、catchをサブクラス型、スーパークラス型の順で待ち受ける場合は、サブクラスのインスタンスならば、サブクラスのcatchで先に捕捉される(例の(2))。
show()は仮想関数としているので、(1)の例でMyExcの例外オブジェクトをcatchでMyExcBaseとして捕捉しても、MyExc.show()を呼び出すことができる。このように、派生したクラスをスーパークラスでまとめて捕捉する方法がある。
#include <iostream>
using namespace std;

class MyExcBase {
public:
    MyExcBase() { cout << "MyExcBase created" << endl; }
    ~MyExcBase() { cout << "MyExcBase deleted" << endl; }
    virtual void show() { cout << "exception MyExcBase" << endl; }
};

class MyExc : public MyExcBase {
public:
    MyExc() { cout << "MyExc created" << endl; }
    ~MyExc() { cout << "MyExc deleted" << endl; }
    void show() { cout << "exception MyExc" << endl; }
};

int main()
{
    try {
        cout << "try start (1)" << endl;
        throw MyExc();
    } catch (MyExcBase& e) {
        cout << "catch MyExcBase" << endl;
        e.show();
    }
    try {
        cout << "try start (2)" << endl;
        throw MyExc();
    } catch (MyExc& e) {
        cout << "catch MyExc" << endl;
        e.show();
    } catch (MyExcBase& e) {
        cout << "catch MyExcBase" << endl;
        e.show();
    }
    return 0;
}
try start (1)
MyExcBase created
MyExc created
catch MyExcBase
exception MyExc
MyExc deleted
MyExcBase deleted
try start (2)
MyExcBase created
MyExc created
catch MyExc
exception MyExc
MyExc deleted
MyExcBase deleted
C++ 例外処理 2024.08.11
コンストラクタから例外送出
コンストラクタの中で送出される例外は、インスタンスの生成がtryブロック内で行われたならば、そのcatchブロックで捕捉できる。
#include <iostream>
using namespace std;

class MyExc {
public:
    MyExc() {
        cout << "MyExc created and throw" << endl;
        throw -1;
    }
};

int main()
{
    try {
        cout << "try start" << endl;
        MyExc myexc;
        cout << "try end" << endl;
    } catch (int e) {
        cout << "catch " << e << endl;
    }
    return 0;
}
try start
MyExc created and throw
catch -1
C++ 例外処理 2024.08.11
関数から例外送出しないnoexcept宣言
関数の後にnoexceptを指定すると、
int func() noexcept;
その関数からは例外は送出しないという宣言になる。noexcept宣言した関数は、全ての例外を関数内で処理する必要がある。
例のようにnoexcept宣言した関数から例外を送出すると、呼び出し元に伝播することなく直ちにstd:terminate()が呼び出され強制終了となる。
#include <iostream>
using namespace std;

void throwex1() noexcept
{
    try {
        cout << "before throw throwex1()" << endl;
        throw -1;
        cout << "after throw throwex1()" << endl;
    } catch (int e) {
        cout << "throwex1 exception int=" << e << endl;
    }
}

void throwex2() noexcept
{
    cout << "before throw throwex2()" << endl;
    throw -1;
    cout << "after throw throwex2()" << endl;
}

int main()
{
    try {
        cout << "try start" << endl;
        throwex1();
        throwex2();
        cout << "try end" << endl;
    } catch (int e) {
        cout << "main exception int=" << e << endl;
    }
    return 0;
}
try start
before throw throwex1()
throwex1 exception int=-1
before throw throwex2()
terminate called after throwing an instance of 'int'

Command terminated
C++ 例外処理 2024.08.11
標準例外
C++標準ライブラリの標準例外としてexceptionクラスが定義されている。
exceptionクラスのwhat()は、その例外の説明の文字列を格納している。標準例外exceptionクラスを基底とした例外の用途ごとのサブクラスが定義されている。
std::bad_alloc newによるメモリ確保失敗
std::runtime_error 実行時エラー
std::logic_error 論理エラー
この例はvectorライブラリのat()が送出する例外を捉えている。更に具体的なサブクラスで待ち受けるならばout_of_rangeクラスである。arr[3]のような言語仕様レベルでの例外処理は実装されていないので、try-catchで捉えることはできない。
#include <vector>
#include <iostream>
#include <exception>
using namespace std;

int main()
{
    try {
        cout << "try start" << endl;
        vector<int> arr = {100, 200, 300};
        cout << arr.at(3);
        cout << "try end" << endl;
    } catch (exception& e) {
        cout << "catch exception" << endl;
        cout << e.what() << endl;
    }
    return 0;
}
try start
catch exception
vector::_M_range_check: __n (which is 3) >= this->size() (which is 3)
C++ 例外処理 2024.08.11
標準例外の送出
論理エラーを意味するlogic_error例外を送出し捕捉する。
exceptionクラスの派生クラスのコンストラクタはメッセージ文字列を引数に指定できる。そのメッセージ文字列はwhat()で取得できる。
#include <iostream>
#include <exception>
using namespace std;

int main()
{
    try {
        cout << "try start" << endl;
        throw logic_error("Logic Error");
        cout << "try end" << endl;
    } catch (logic_error& e) {
        cout << "catch exception" << endl;
        cout << e.what() << endl;
    }
    return 0;
}
try start
catch exception
Logic Error
C++ 例外処理 2024.08.11
exceptionの派生例外クラスを定義
exceptionクラスを基底とするMyExcクラスを定義する。
標準例外の構成に則り、コンストラクタで例外メッセージを受け取り、仮想関数what()を実装してそのメッセージを返している。
#include <iostream>
#include <exception>
using namespace std;

class MyExc : public std::exception {
    const char* message;
public:
    MyExc(const char* msg) : message(msg) {}
    virtual const char* what() const noexcept override {
        return message;
    }
};

int main()
{
    try {
        cout << "try start" << endl;
        throw MyExc("my exception");
        cout << "try end" << endl;
    } catch (MyExc& e) {
        cout << "catch exception" << endl;
        cout << e.what() << endl;
    }
    return 0;
}
try start
catch exception
my exception
C++ 例外処理 2024.08.11
ゼロ除算は例外で捕捉できない
C++言語ではゼロ除算を例外として捕捉できない。
例は、ゼロ除算がexceptionクラスの何かの派生例外で捕捉することを期待しているが、catchには入らずにプログラムが異常終了している。分母が0になる除算のチェックと例外送出などのエラー処理は、自ら実装しなければならない。
#include <iostream>
#include <exception>
using namespace std;

int main()
{
    try {
        cout << "try start" << endl;
        int a = 0;
        int b = 10 / a;
        cout << "try end" << endl;
    } catch (exception& e) {
        cout << e.what() << endl;
    } catch (...) {
        cout << "unknown exception" << endl;
    }
    return 0;
}
try start

Command terminated