例外クラスの設計


9

小さなライブラリをコーディングしていますが、例外処理の設計に問題があります。私はC ++言語のこの機能に(まだ)混乱していると言わざるを得ません。例外クラスを適切に処理するために何をしなければならないかを理解するために、この件について可能な限り読んでみました。

クラスのsystem_errorSTL実装からインスピレーションを得たタイプのアプローチを使用することにしましたfuture_error

エラーコードを含む列挙があります:

enum class my_errc : int
{
    error_x = 100,
    error_z = 101,
    error_y = 102
};

そして、単一の例外クラス(error_category構造のタイプとsystem_errorモデルが必要とする他のすべてによってバックアップされます):

// error category implementation
class my_error_category_impl : public std::error_category
{
    const char* name () const noexcept override
    {
        return "my_lib";
    }

    std::string  message (int ec) const override
    {
        std::string msg;
        switch (my_errc(ec))
        {
        case my_errc::error_x:
            msg = "Failed 1.";
            break;
        case my_errc::error_z:
            msg = "Failed 2.";
            break;
        case my_errc::error_y:
            msg = "Failed 3.";
            break;
        default:
            msg = "unknown.";
        }

        return msg;
    }

    std::error_condition default_error_condition (int ec) const noexcept override
    {
        return std::error_condition(ec, *this);
    }
};

// unique instance of the error category
struct my_category
{
    static const std::error_category& instance () noexcept
    {
        static my_error_category_impl category;
        return category;
    }
};

// overload for error code creation
inline std::error_code make_error_code (my_errc ec) noexcept
{
    return std::error_code(static_cast<int>(ec), my_category::instance());
}

// overload for error condition creation
inline std::error_condition make_error_condition (my_errc ec) noexcept
{
    return std::error_condition(static_cast<int>(ec), my_category::instance());
}

/**
 * Exception type thrown by the lib.
 */
class my_error : public virtual std::runtime_error
{
public:
    explicit my_error (my_errc ec) noexcept :
        std::runtime_error("my_namespace ")
        , internal_code(make_error_code(ec))
    { }

    const char* what () const noexcept override
    {
        return internal_code.message().c_str();
    }

    std::error_code code () const noexcept
    {
        return internal_code;
    }

private:
    std::error_code internal_code;
};

// specialization for error code enumerations
// must be done in the std namespace

    namespace std
    {
    template <>
    struct is_error_code_enum<my_errc> : public true_type { };
    }

エラーコードの列挙で示される例外をスローする状況はごくわずかです。

上記は私のレビュアーの一人とうまくいっていませんでした。std::runtime_error条件にエラーコードを埋め込むと、例外とエラーコードが混ざり合うため、派生クラスから派生した例外クラスの階層を作成するべきだったと彼は考えていました。取り扱いの; 例外階層により、エラーメッセージを簡単にカスタマイズすることもできます。

私の引数の1つが、私は私のライブラリは、例外の複数の種類をスローする必要はなかったこと、それをシンプルに維持したいということでしたし、自動的に処理されるようカスタマイズは、この場合にも簡単であることを- error_codeしているerror_category翻訳すること、それに関連付けられています適切なエラーメッセージのコード。

私は自分の選択をうまく守っていなかったと言わざるを得ません。C++の例外に関してまだいくつかの誤解があるという事実の証です。

私のデザインが意味を成しているかどうか知りたいのですが。私もそれを見落とすことを認めなければならないので、私が選択した方法よりも他の方法の利点は何ですか?改善するにはどうすればよいですか?


2
私は原則としてあなたのレビュアーに同意する傾向があります(エラーコードと例外を混在させることはそれほど役に立ちません)。しかし、大きな階層を持つ巨大なライブラリがない限り、それも役に立ちません。メッセージ文字列を含む基本例外は、例外のキャッチャーが例外の一意性を潜在的に使用して問題を修正できる場合にのみ、別個の例外があります。
マーティンヨーク

回答:


9

私はあなたの同僚は正しかったと思います:あなたは、クライアントコードの例外処理のニーズに基づくのではなく、階層内での実装がいかに簡単であるかに基づいて例外ケースを設計しています。

1つの例外タイプとエラー条件(ソリューション)の列挙を使用して、クライアントコードが単一のエラーケース(たとえばmy_errc::error_x)を処理する必要がある場合、次のようなコードを記述する必要があります。

try {
    your_library.exception_thowing_function();
} catch(const my_error& err) {
    switch(err.code()) { // this could also be an if
    case my_errc::error_x:
        // handle error here
        break;
    default:
        throw; // we are not interested in other errors
    }
}

複数の例外タイプ(階層全体に共通のベースを持つ)を使用すると、次のように記述できます。

try {
    your_library.exception_thowing_function();
} catch(const my_error_x& err) {
    // handle error here
}

例外クラスは次のようになります。

// base class for all exceptions in your library
class my_error: public std::runtime_error { ... };

// error x: corresponding to my_errc::error_x condition in your code
class my_error_x: public my_error { ... };

ライブラリを作成するときは、内部実装の(必ずしも)容易さではなく、その使いやすさを重視する必要があります。

ライブラリで正しく実行する努力が非常に困難な場合にのみ、使いやすさ(クライアントコードの外観)を妥協する必要があります。


0

私はあなたのレビュアーと@utnapistimに同意します。system_error一部のエラーで特別な処理が必要な場合に、クロスプラットフォームのものを実装するときにアプローチを使用できます。しかし、この場合でも、それは良い解決策ではありませんが、それほど邪悪な解決策ではありません。

もう一つ。例外階層を作成するときは、あまり深くしないでください。クライアントが処理できる例外クラスのみを作成します。ほとんどの場合、私はとのみを使用std::runtime_errorstd::logic_errorます。私は投げるstd::runtime_error何かがうまくstd::logic_errorいかず、何もできないとき(ユーザーがコンピューターからデバイスを取り出し、そのアプリケーションがまだ実行されていないことを忘れたとき)し、プログラムロジックが壊れたとき(ユーザーが存在しないデータベースからレコードを削除しようとしたが、削除操作の前に確認できるので、論理エラーが発生します)。

ライブラリ開発者として、ユーザーのニーズについて考えてください。自分で使ってみて、自分にとって快適かどうかを考えてください。コードの例を使用して、レビューアに自分の立場を説明できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.