2017-01-15 2 views
0

Ich glaube, es ist legal, * dies aus dem Körper eines Konstruktors zu werfen. Ich würde gerne wissen, ob ich mich einfach geirrt habe (Probleme, ein Objekt zu umgehen, bevor der Konstruktor fertig ist?) Oder ob das stilistisch verabscheuungswürdig ist.Bedingungslos * dies vom Konstruktor

Ich postete eine zweite, spezifischere Frage here, die umfassend beantwortet wurde. Der Code in diesem Beitrag ist legal, wenn auch etwas seltsam.

Gegeben eine Ausnahme struct:

struct fail final : public std::logic_error 
{ 
    fail() : std::logic_error("fail") {} 
    using std::logic_error::logic_error; 
}; 

und eine Reihe von Call-Sites wie:

throw fail(); 
// or 
throw fail("oh dear"); 

Ich überlege die Struktur zu ändern sein:

struct fail final : public std::logic_error 
{ 
    fail() : std::logic_error("fail") { throw * this; } 
    fail(const char* w) : std::logic_error(w) { throw * this; } 
}; 

Woraufhin Die Call-Sites können unverändert bleiben oder als kürzere umgeschrieben werden:

fail(); 
// or 
fail("oh dear"); 

Dies bedeutet im Wesentlichen, dass ich nicht mehr überall werfen muss. Ich kann auch weiterhin die Ausnahme mit dem Namen "fail" abfangen. Es scheint sicherlich zu funktionieren, aber lässt mich mit dem Verdacht, dass ich diese Wahl später bereuen könnte.

Danke

bearbeiten: Gedanken etwas mehr über das Verhalten.

1/Wurf * diese entweder eine Kopie * dies machen wird oder es bewegen, wenn dies als Freiwild für die Kopie elision zählt, so Brennen der Destruktor von logic_error ist kein Problem

2/The Standardkopiekonstruktor einer Klasse ohne Mitglieder ist wahrscheinlich nur der Kopierkonstruktor der Basis, also kann wahrscheinlich das kopieren

3/Die Kopie von * diese, die über die Ausnahme zurückgegeben wird, hat wahrscheinlich undefinierte Werte für alle Mitglieder, die sind nicht in der Initialisierungsliste gesetzt

4/Elementfunktionen können während des Aufbaus aufgerufen werden. Der (Standard) Kopierkonstruktor ist eine Elementfunktion und kann daher während der Konstruktion aufgerufen werden. throw * Dies ruft den Kopierkonstruktor auf. Deshalb glaube ich, noch der Code legal ist

+2

Was über das Schreiben 'throw' so schlecht ist? Ich kann nicht glauben, dass Sie all das durchgehen, nur um den Code schwerer lesbar zu machen. –

+0

Das Hauptproblem, das ich sehe, ist, dass Sie die Tatsache verbergen, dass Sie eine Ausnahme von den Menschen, die Ihren Code lesen, werfen. –

+1

Wenn Sie wirklich vermeiden wollen, throw immer und immer wieder zu wiederholen, sollten Sie Ihre Fehlerbehandlungsanmeldung in eine * Funktion höherer Ordnung * einbetten, die ** den gesamten Fehlerbehandlungsablauf enthält. Entweder abstrahiere alles oder nichts davon. –

Antwort

2

Ich habe Sympathie mit Ihrem Wunsch, ausdrucksstarken Code zu schreiben.

Dies ist die Art der Sache, die ich für die Verwendung in unserer Codebasis entwickelt habe, insbesondere beim Aufruf von C-APIs, die Erfolg oder Fehlschlag als boolesche Ganzzahl zurückgeben.

#include <stdexcept> 
#include <exception> 

struct failure : std::runtime_error 
{ 
    using runtime_error::runtime_error; 
}; 

template<class Message> 
[[noreturn]] 
bool fail(Message&& msg) 
{ 
    throw failure(std::forward<Message>(msg)); 
} 

int main() 
{ 
    extern bool didSomething(); 

    didSomething() or fail("couldn't do it"); 
} 

Mehr Spaß mit Ausnahmen:

#include <stdexcept> 
#include <exception> 
#include <cstdio> 
#include <memory> 
#include <system_error> 
#include <sstream> 
#include <iostream> 
#include <iomanip> 


namespace native 
{ 
    struct no_message {}; 

    constexpr no_message join() { return {}; } 

    template<class First, class...Rest> 
    std::string join(First&& first, Rest&&...rest) 
    { 
     std::ostringstream ss; 
     ss << first; 
     using expand = int[]; 
     void(expand{ 0, 
        ((ss << ' ' << rest),0)... 
     }); 
     return ss.str(); 
    } 

    [[noreturn]] 
    void throwSystemError(no_message, int code) 
    { 
     throw std::system_error(code, std::system_category()); 
    } 

    template<class Message> 
    [[noreturn]] 
    void throwSystemError(Message&& message, int code) 
    { 
     throw std::system_error(code, std::system_category(), message); 
    } 

    template<class...Parts> 
    [[noreturn]] 
    bool systemError(Parts&&...parts) 
    { 
     auto err = errno; 
     throwSystemError(join(std::forward<Parts>(parts)...), err); 
    } 

    struct file_closer { 
     void operator()(FILE* fp) const noexcept { 
      std::fclose(fp); 
     } 
    }; 
    using FilePtr = std::unique_ptr<FILE, file_closer>; 

    bool valid(FilePtr const& p) { return p.get(); } 

    FilePtr openFile(const char* path, const char* mode) 
    { 
     auto ptr = FilePtr(std::fopen(path, mode)); 
     valid(ptr) or systemError("opening file", std::quoted(path), "in mode", std::quoted(mode)); 
     return ptr; 
    } 
} 


int main() 
try 
{ 
    auto fptr = native::openFile("ibetthisdoesntexist.txt", "rb"); 
} 
catch(std::system_error const& syserr) 
{ 
    std::cerr << "system error: " 
       << syserr.what() 
       << ", error code " << syserr.code().value() 
       << std::endl; 
    std::exit(100); 
} 

Beispiel Ausgabe:

system error: opening file "ibetthisdoesntexist.txt" in mode "rb": No such file or directory, error code 2 
+1

Verwenden Sie tatsächlich die alternativen Tokens von ISO 646? Das scheint so, als würde man "expressiven Code" auf die Spitze treiben, wo man idiomatisches C++ opfert. So wie es ist, bin ich nicht sicher, ob das Verstecken einer "throw" -Anweisung der Lesbarkeit einen Gefallen tut. –

+1

Ich verwende die ganze Zeit ISO 646 für boolesche Logik. Es ist nicht nur lesbarer, es ist auch unmöglich zu schreiben '&', wenn Sie '&&' schreiben wollten. Der 'Raise'-Aufruf ist aus mehreren Gründen nützlich. Erstens, es verschiebt den Wurf, also kann ich mit sehr wenig Refactoring (zum Beispiel) Logging oder Metriken an der Wurfstelle einfügen. Die zweite ist, dass ich eine ungesunde Zuneigung für Skriptsprachen wie Perl habe :) –

+0

Interessant. Ich mag die Verwendung von fail für das Verb und Fehler für das Substantiv. Dies erklärt auch den Code gut. Vielen Dank –

2

Die beiden Nachteile, die ich sehe:

  • nicht idiomatischen, so wird es Benutzern überraschen, dass es eine versteckte throw ist. Also IMO, es ist weniger lesbar.

  • Sie können nicht von Ihrer Klasse erben (wie diese Basisklasse löst) (Sie können die Klasse final (aus C++ 11) markieren, um sie hervorzuheben).

+0

Ich stimme weitgehend überein, nicht-idiomatisch. Es schien merkwürdig genug, um diese Frage doch zu motivieren. Die Vererbungsbeschränkung ist eine großartige Beobachtung, ich habe die Struktur als final markiert (im experimentellen Zweig und in der Frage), danke –

Verwandte Themen