İstisna sınıfları tasarlama


9

Küçük bir kütüphaneyi kodluyorum ve istisna yönetimini tasarlamakta sorun yaşıyorum. C ++ dilinin bu özelliğiyle (hala) kafam karıştığımı söylemeliyim ve istisna sınıflarıyla düzgün çalışmak için ne yapmam gerektiğini anlamak için konuyla ilgili mümkün olduğunca fazla okumaya çalıştım.

Sınıfın system_errorSTL uygulamasından ilham alan bir tür yaklaşım kullanmaya karar verdim future_error.

Hata kodlarını içeren bir numaralandırma var:

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

ve tek bir istisna sınıfı (bir error_categorytür yapı ve system_errormodelin ihtiyaç duyduğu her şey tarafından desteklenir ):

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

Hata kodu numaralandırması ile gösterilen istisnaları attığım durumlar çok az.

Yukarıdaki benim yorumculardan biri ile iyi oturmadı. Durumunda std::runtime_errorgömülü hata kodunun bazı şeyleri - istisnaları ve hata kodlarını - karıştırması nedeniyle türetilmiş bir temel sınıf ile bir istisna sınıfı hiyerarşisi oluşturmam gerektiğine inanıyordu ve bir noktayla uğraşmak daha sıkıcı olurdu elleçleme; kural dışı durum hiyerarşisi hata mesajının kolayca özelleştirilmesini de sağlar.

Benim argümanlardan biri Kütüphanemin istisnalar birden fazla türde atmak gerek olmadığını, basit tutmak istedim oldu ve otomatik olarak işlenir olarak özelleştirme bu durumda kolay da olduğu - error_codebir etti error_categoryçevirir o onunla ilişkili uygun hata iletisini kodlayın.

C ++ istisnaları ile ilgili hala bazı yanlış anlaşılmalara sahip olduğumun kanıtı, seçimimi iyi bir şekilde savunmadım.

Tasarımımın mantıklı olup olmadığını bilmek istiyorum. Bunu da göremediğimi itiraf etmem gerektiğinden, diğer yöntemin seçtiğim yöntemden avantajları ne olurdu? Geliştirmek için ne yapabilirim?


2
Prensipte yorumcunuzla hemfikirim (hata kodlarını ve istisnaları karıştırmak gerçekten kullanışlı değil). Ancak büyük bir hiyerarşiye sahip büyük bir kütüphaneniz olmadığı sürece de işe yaramaz. İleti dizesi içeren bir temel kural dışı durum, yalnızca kural dışı durumun yakalayıcısı sorunu çözmek için kural dışı durumun benzersizliğini kullanabiliyorsa ayrı ayrı istisnalara sahiptir.
Martin York

Yanıtlar:


9

İş arkadaşınızın haklı olduğunu düşünüyorum: İstisna durumlarınızı, istemci kodunun istisna işleme gereksinimlerine değil, hiyerarşi içinde uygulamanın ne kadar basit olduğuna göre tasarlıyorsunuz.

Bir istisna türü ve hata durumu (çözümünüz) için bir numaralandırma ile, istemci kodunun tek hata durumlarını (örneğin my_errc::error_x) işlemesi gerekiyorsa, aşağıdaki gibi bir kod yazmaları gerekir:

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
    }
}

Birden fazla istisna türüyle (tüm hiyerarşi için ortak bir tabana sahip) aşağıdakileri yazabilirsiniz:

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

istisna sınıfları şöyle görünür:

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

Bir kütüphane yazarken, odak noktası iç uygulamanın kolaylığı değil, kullanım kolaylığı üzerinde olmalıdır.

Kullanım kolaylığından (istemci kodunun nasıl görüneceği gibi) yalnızca kütüphanede doğru çaba gösterilmesi yasak olduğunda ödün vermelisiniz.


0

Hakemlerinize ve @utnapistim'e katılıyorum. Kullanabilirsinizsystem_errorBazı hatalar özel işlem gerektirdiğinde platformlar arası şeyler uygularken yaklaşımı . Ancak bu durumda bile, iyi bir çözüm değil, daha az kötü bir çözümdür.

Bir şey daha. İstisna hiyerarşisi oluştururken, onu çok derin yapmayın. Yalnızca istemciler tarafından işlenebilecek istisna sınıflarını oluşturun. Çoğu durumda sadece std::runtime_errorve std::logic_error. Bir std::runtime_errorşey ters gittiğinde atıyorum ve hiçbir şey yapamıyorum (kullanıcı cihazı bilgisayardan çıkarıyor, o uygulamanın hala çalıştığını unutuyor) ve std::logic_errorprogram mantığı bozulduğunda (Kullanıcı mevcut olmayan veritabanından kaydı silmeye çalışıyor, ancak silme işleminden önce kontrol edebilir, böylece mantık hatası alır).

Kütüphane geliştiricisi olarak kullanıcılarınızın ihtiyaçlarını düşünün. Kendiniz kullanmaya çalışın ve sizin için rahat olup olmadığını düşünün. Daha sonra kod örnekleriyle yorumcunuza konumunuzu açıklayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.