2017-02-26 1 views
2

Ich möchte, dass meine Funktionen entweder eine Erfolgsmeldung oder ein Objekt zurückgeben, das die Art des Fehlers beschreibt. Ich würde normalerweise Ausnahmen dafür verwenden, aber mir wurde gesagt, sie nicht für allgemeine Codepfade zu verwenden, und dieser Satz von Funktionen kann aus verschiedenen Gründen ziemlich oft scheitern.Wie gibt man Erfolg oder ein Fehlerobjekt von Funktionen zurück?

Mein Gedanke ist, C++ 17's std::optional zu verwenden, da ich kein vollständiges Fehlerobjekt zurückgeben muss, wenn es nicht benötigt wird. Also mit optional, wenn die Funktion nicht erfolgreich ist, gibt es das Fehlerobjekt zurück, sonst ist das optionale leer. Das Problem damit ist, dass es die Erwartung eines zurückgegebenen Werts umkehrt (d. H. true zeigt normalerweise Erfolg, nicht Fehler an).

Ich konnte Leute bekommen eine is_success Funktion zu verwenden, die wie folgt verwendet werden würde, Klasse mein Fehler Error vorausgesetzt:

auto result = do_stuff(); 
if (!is_success(result)) { 
    Error err = *result; 
    // ... 
} 

Oder ein Ergebnis Klasse wäre robuster?

class MaybeError { 
    std::optional<Error> _error; 
public: 
    MaybeError(const Error& error) : _error(error) {} 
    constexpr MaybeError() : _error({}) {} 

    explicit operator bool const() { 
     return !(_error.operator bool()); 
    } 
    constexpr bool has_error() const { 
     return !(_error.has_value()); 
    } 

    constexpr Error& error() & { return _error.value(); } 
    constexpr const Error & error() const & { return _error.value(); } 
    constexpr Error&& error() && { return std::move(_error.value()); } 
    constexpr const Error&& error() const && { return std::move(_error.value()); } 

    constexpr const Error* operator->() const { return _error.operator->(); } 
    constexpr Error* operator->() { return _error.operator->(); } 
    constexpr const Error& operator*() const& { return _error.operator*(); } 
    constexpr Error& operator*() & { return _error.operator*(); } 
    constexpr const Error&& operator*() const&& { return std::move(_error.operator*()); } 
    constexpr Error&& operator*() && { return std::move(_error.operator*()); } 
}; 

, die ähnlich dem ersten Beispiel verwendet werden können:

auto result = do_stuff(); 
if (!result) { 
    Error err = *result; 
    // ... 
} 

Was ist die beste Option? Gehe ich das richtig? gelingt


bearbeiten Um klar zu sein, die do_stuff Funktion, die, wenn sie nicht zurückgibt ein Objekt genannt wird. Wenn es immer ohne Fehler gelingen würde, wäre es einfach void do_stuff().


Edit 2 In den Kommentaren schlägt Christian Hackl eine weniger over-engineered Lösung, die eine einfache Struktur verwendet wird.

struct MaybeError { 
    std::optional<Error> error; 
}; 

Dies würde sowohl einfacher sein und meine Sorge sprechen, dass die Menschen Funktionen wahr zurückzukehren erwarten würden, wenn erfolgreich, indem es ausdrücklich, dass es der Fehler machen, die für die getestet wird. Zum Beispiel:

auto result = do_stuff(); 
if (result.error) { 
    Error e = *t.error; 
    // ... 
} 
+4

Dies scheint bemerkenswert kompliziert, wenn die Verwendung von Ausnahmen viel sauberer und einfacher wäre und weniger Code benötigt. –

+2

Ich würde gerne Ausnahmen verwenden, aber leider hat mein Chef starke Gefühle zu diesem Thema. – ChrisD

+2

Er ist ein Idiot. Ausnahmen sind in gutem C++ - Code nicht optional. –

Antwort

2

Ich spielte ein bisschen mit boost::expected das war suggested by Nicol Bolas.Es scheint wirklich schön für diesen Anwendungsfall zu sein:

#include <iostream> 
#include <system_error> 
#include <boost/expected/expected.hpp> 

using ExpectedVoid = boost::expected< void, std::error_code >; 
using ExpectedInt = boost::expected< int, std::error_code >; 

ExpectedVoid do_stuff(bool wantSuccess) { 
    if(wantSuccess) 
     return {}; 
    return boost::make_unexpected(std::make_error_code(std::errc::operation_canceled)); 
} 

ExpectedInt do_more_stuff(bool wantSuccess) { 
    if(wantSuccess) 
     return 42; 
    return boost::make_unexpected(std::make_error_code(std::errc::operation_canceled)); 
} 

int main() 
{ 
    for(bool wantSuccess : { false, true }) 
    { 
     if(auto res = do_stuff(wantSuccess)) 
      std::cout << "do_stuff successful!\n"; 
     else 
      std::cout << "do_stuff error: " << res.error() << "\n"; 
    } 

    std::cout << "\n"; 

    for(bool wantSuccess : { false, true }) 
    { 
     if(auto res = do_more_stuff(wantSuccess)) 
      std::cout << "do_more_stuff successful! Result: " << *res << "\n"; 
     else 
      std::cout << "do_more_stuff error: " << res.error() << "\n"; 
    } 

    return 0; 
} 

Ausgang:

do_stuff error: generic:105 
do_stuff successful! 

do_more_stuff error: generic:105 
do_more_stuff successful! Result: 42 

Sie die Quelle am Anfang aus dem Link herunterladen können und werfen Sie einfach die Dateien aus dem „include“ Verzeichnis Die Quelle in Ihrem Boost enthalten Verzeichnis ("boost" Unterordner).

2

Es gibt Arten dafür entworfen. Eine proposed for standardization (PDF) ist expected<T, E>. Es ist im Grunde wie eine Variante, die entweder den gewünschten Wert oder einen "Fehlercode" haben kann (T kann void sein, wenn Sie nur überprüfen möchten, ob ein Prozess erfolgreich war).

Natürlich können Sie einen tatsächlichenvariant verwenden, wenn Sie Zugriff auf eine solche Implementierung haben. Aber expected hat eine schönere Schnittstelle, die für dieses Szenario entwickelt wurde. Und im Gegensatz zu std::variant von C++ 17 kann der vorgeschlagene expected nicht valueless_by_exception sein, da E erforderlich ist, um ein nicht-beweglicher Typ (nicht genau ein hoher Balken für die meisten Fehlercodes) zu sein.

+0

Mein einziges Problem damit ist, dass meine do_stuff-Funktion bei Erfolg nichts zurückgeben muss (siehe meine Bearbeitung). Also wird der T-Typ redundant sein. Ich schätze, ich könnte einen Bool verwenden und ihn immer auf "true" setzen. – ChrisD

+1

@ChrisD: Aus diesem Grund können Sie mit 'expected'' T' auf 'void' setzen. –

+0

Es gibt tatsächlich eine [Implementierung von erwartet] (https://github.com/ptal/expected) - von einem schnellen Blick darauf ist es wirklich cool! Siehe Beispiel Verwendung in meiner Antwort. – zett42

Verwandte Themen