exception class。

例外処理(C++)で送出するために設計されたクラス型(C++)

概要

intconst char*のような基本型を例外として送出することもできるが、それだけでは何が起きたのかが曖昧になりやすい。

例外クラスを使うと、

  • 何の種類の例外かを型で区別できる
  • エラーメッセージなどの追加情報を持たせられる
  • 例外ごとに異なるcatchで処理できる

class ArrayException
{
private:
    std::string m_error{};
 
public:
    ArrayException(std::string_view error)
        : m_error{ error }
    {
    }
 
    const std::string& getError() const
    {
        return m_error;
    }
};
if (index < 0 || index >= getLength())
    throw ArrayException{ "Invalid index" };

このように、例外クラスを送出すると、どのクラスから発生したどの種類の異常かを表現しやすくなる。

受け取り方

クラス型の例外は、通常const参照で受け取る。

catch (const ArrayException& exception)
{
    std::cerr << exception.getError() << '\n';
}

これは不要なコピーを避けるためであり、オブジェクトスライシングを防ぐ意味もある。
ポインタで受け取る方法は、特別な理由がない限り通常は使わない。

継承との関係

例外クラスも通常のクラスと同じように継承(C++)できる。
そのため、基底例外クラスを作り、その派生例外クラスをまとめて扱うことができる。

class Base {};
class Derived : public Base {};
 
try
{
    throw Derived{};
}
catch (const Base&)
{
    std::cerr << "caught Base";
}
catch (const Derived&)
{
    std::cerr << "caught Derived";
}

この例ではDerivedが送出されても、先にcatch (const Base&)が一致するため、Derived用のcatchには到達しない。

そのため、派生例外クラス用のcatchは、基底例外クラス用のcatchより先に書く必要がある。

std::exception

標準ライブラリの多くの例外クラスは、<exception>ヘッダのstd::exceptionを基底クラスとしている。

そのため、

catch (const std::exception& exception)
{
    std::cerr << exception.what() << '\n';
}

のように書くことで、標準ライブラリ由来の多くの例外をまとめて受け取れる。

std::exceptionには仮想関数what()があり、例外の説明文字列を返す。
多くの派生例外クラスはこのwhat()をオーバーライドしている。

独自例外クラスをstd::exceptionから派生させる

独自例外クラスもstd::exceptionstd::runtime_errorから派生させることができる。

class ArrayException : public std::runtime_error
{
public:
    ArrayException(const std::string& error)
        : std::runtime_error{ error }
    {
    }
};

std::runtime_errorを使うと、エラーメッセージの保持やwhat()の実装を自分で書かずに済む。

一方、std::exceptionから直接派生する場合は、必要に応じてwhat() const noexceptをオーバーライドする。

関連

参考