예외 클래스 설계


9

작은 라이브러리를 코딩 중이며 예외 처리를 디자인하는 데 문제가 있습니다. 나는 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조건에 포함 된 오류 코드가 예외와 오류 코드를 혼합하여 사물을 혼합하기 때문에 파생 된 기본 클래스를 사용하여 예외 클래스의 계층 구조를 만들어야한다고 생각 했습니다. 취급 예외 계층 구조는 오류 메시지를 쉽게 사용자 정의 할 수있게합니다.

내 주장 중 하나는 내 라이브러리 예외의 여러 유형을 던질 필요가 없었다 것으로, 간단하게하고 싶다고했고이 자동으로 처리함에 따라 사용자 정의는이 경우 쉽게도 있음 -가 error_code있다 error_category변환 그와 연관된 올바른 오류 메시지를 표시하십시오.

나는 C ++ 예외에 대해 여전히 오해가 있다는 사실을 증명하면서 내 선택을 잘 지키지 않았다고 말해야합니다.

내 디자인이 의미가 있는지 알고 싶습니다. 내가 인정하지 않아도 선택한 방법에 비해 다른 방법의 장점은 무엇입니까? 개선하기 위해 무엇을 할 수 있습니까?


2
나는 리뷰어와 원칙적으로 동의하는 경향이 있습니다 (오류 코드와 예외를 섞는 것은 실제로 그렇게 유용하지는 않습니다). 그러나 큰 계층 구조를 가진 거대한 라이브러리가 없다면 유용하지 않습니다. 메시지 문자열을 포함하는 기본 예외는 예외의 캐처가 잠재적으로 예외의 고유성을 사용하여 문제점을 해결할 수있는 경우 별도의 예외를 갖습니다.
Martin York

답변:


9

동료가 옳다고 생각합니다. 클라이언트 코드의 예외 처리 요구 사항이 아니라 계층 구조 내에서 구현하는 것이 얼마나 간단한 지에 따라 예외 사례를 설계하고 있습니다.

하나의 예외 유형과 오류 조건 (솔루션)에 대한 열거를 사용하여 클라이언트 코드가 단일 오류 사례 (예 :)를 처리해야하는 경우 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_error및 만 사용합니다 std::logic_error. 나는 std::runtime_error무언가 잘못되었을 때 던지고 아무것도 할 수 없다 (사용자는 컴퓨터에서 장치를 꺼내고, 응용 프로그램이 여전히 실행 중임을 잊어 버렸다), std::logic_error프로그램 논리가 깨졌을 때 (사용자는 존재하지 않는 데이터베이스에서 레코드를 삭제하려고 시도하지만 삭제 작업 전에 그는 그것을 확인할 수 있으므로 논리 오류가 발생합니다).

라이브러리 개발자는 사용자의 요구에 대해 생각하십시오. 자신을 편하게 사용하고 편안하게 생각하십시오. 코드 예제를 통해 검토 자에게 자신의 입장을 설명 할 수있는 것보다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.