web-dev-qa-db-ja.com

例外クラスの設計

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

system_errorクラスのSTL実装から着想を得て、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++の例外に関してまだいくつかの誤解があるという事実の証拠である私の選択をうまく守っていなかったと言わなければなりません。

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

9
celavek

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

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 { ... };

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

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

9
utnapistim

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

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

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

0
user174740