Ich codiere eine kleine Bibliothek und habe Probleme beim Entwerfen der Ausnahmebehandlung. Ich muss sagen, dass ich (immer noch) durch diese Funktion der C ++ - Sprache verwirrt bin und versucht habe, so viel wie möglich zu diesem Thema zu lesen, um zu verstehen, was ich tun müsste, um mit Ausnahmeklassen richtig zu arbeiten.
Ich entschied mich für einen system_error
Ansatz, der sich von der STL-Implementierung der future_error
Klasse inspirieren ließ .
Ich habe eine Aufzählung mit den Fehlercodes:
enum class my_errc : int
{
error_x = 100,
error_z = 101,
error_y = 102
};
und eine einzelne Ausnahmeklasse (gesichert durch eine error_category
Art von Strukturen und alles andere, was das system_error
Modell benötigt ):
// 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 { };
}
Ich habe nur eine kleine Anzahl von Situationen, in denen ich Ausnahmen auslöse, die durch die Aufzählung der Fehlercodes veranschaulicht werden.
Das oben Gesagte passte nicht gut zu einem meiner Rezensenten. Er war der Meinung, dass ich eine Hierarchie von Ausnahmeklassen mit einer abgeleiteten Basisklasse std::runtime_error
hätte erstellen sollen, da der in die Bedingung eingebettete Fehlercode Dinge vermischt - Ausnahmen und Fehlercodes - und es mühsamer wäre, sich mit dem Punkt zu befassen der Handhabung; Die Ausnahmehierarchie würde auch eine einfache Anpassung der Fehlermeldung ermöglichen.
Eines meiner Argumente war, dass ich es einfach halten wollte, dass meine Bibliothek nicht mehrere Arten von Ausnahmen auslösen musste und dass die Anpassung auch in diesem Fall einfach ist, da sie automatisch durchgeführt wird - dem error_code
ist eine error_category
zugeordnet, die das übersetzt Code zur richtigen Fehlermeldung.
Ich muss sagen, dass ich meine Wahl nicht gut verteidigt habe, ein Beweis dafür, dass ich immer noch einige Missverständnisse in Bezug auf C ++ - Ausnahmen habe.
Ich würde gerne wissen, ob mein Design Sinn macht. Was wären die Vorteile der anderen Methode gegenüber der von mir gewählten, da ich zugeben muss, dass ich das auch nicht sehe? Was könnte ich tun, um mich zu verbessern?
quelle
Antworten:
Ich denke, Ihr Kollege hatte Recht: Sie entwerfen Ihre Ausnahmefälle basierend auf der einfachen Implementierung innerhalb der Hierarchie und nicht basierend auf den Ausnahmebehandlungsanforderungen des Client-Codes.
Wenn der Clientcode mit einem Ausnahmetyp und einer Aufzählung für die Fehlerbedingung (Ihre Lösung) einzelne Fehlerfälle behandeln muss (z. B.
my_errc::error_x
), muss er Code wie folgt schreiben:Mit mehreren Ausnahmetypen (mit einer gemeinsamen Basis für die gesamte Hierarchie) können Sie schreiben:
wo Ausnahmeklassen so aussehen:
Beim Schreiben einer Bibliothek sollte der Schwerpunkt auf der Benutzerfreundlichkeit und nicht (notwendigerweise) auf der Einfachheit der internen Implementierung liegen.
Sie sollten die Benutzerfreundlichkeit (wie Client-Code aussehen wird) nur dann beeinträchtigen, wenn der Aufwand, ihn direkt in der Bibliothek auszuführen, unerschwinglich ist.
quelle
Ich stimme Ihren Rezensenten und @utnapistim zu. Sie können den
system_error
Ansatz verwenden, wenn Sie plattformübergreifende Dinge implementieren, wenn einige Fehler eine spezielle Behandlung erfordern. Aber auch in diesem Fall ist es keine gute, sondern eine weniger böse Lösung.Eine Sache noch. Machen Sie beim Erstellen einer Ausnahmehierarchie diese nicht sehr tief. Erstellen Sie nur die Ausnahmeklassen, die von Clients verarbeitet werden können. In den meisten Fällen benutze ich nur
std::runtime_error
undstd::logic_error
. Ich werfe,std::runtime_error
wenn etwas schief geht und ich nichts tun kann (Benutzer wirft Gerät vom Computer aus, er hat vergessen, dass die Anwendung noch läuft), undstd::logic_error
wenn die Programmlogik unterbrochen ist (Benutzer versucht, einen Datensatz aus einer nicht vorhandenen Datenbank zu löschen, aber vor dem Löschvorgang er kann es überprüfen, so dass er einen logischen Fehler bekommt).Denken Sie als Bibliotheksentwickler an die Bedürfnisse Ihrer Benutzer. Versuchen Sie es selbst zu benutzen und überlegen Sie, ob es Ihnen tröstet. Dann können Sie Ihren Prüfern Ihre Position anhand von Codebeispielen erläutern.
quelle