Designfrage Rückgabeparameter
-
Hallo,
ich habe eine Funktion, die eine Datenstruktur aus einer Datei ausliest. Dabei schließt der Anwendungsfall ein, dass die Datei durch einen anderen Prozess gelockt ist und nicht geöffnet werden kann, das ist ein gültiges Szenario.
Wie sollte die Signatur für diese Funktion aussehen (C++11)?Option 1)
unsigned int read_struct( const string& file_name, StructType& data )
Naiver Ansatz, die Funktion füllt die Datenstruktur und gibt einen Status zurück. Fühlt sich mit den Möglichkeiten von C++ aber iwie komisch an. Außerdem muss mandata
vorher anlegen, finde ich auch nicht schön.Option 2)
std::tuple<unsigned int, StructType> read_struct( const std::string& file_name )
Element 0 hält den Fehlercode, Element 1 die Daten. Wenn die Datei nicht gelesen werden konnte mussStructType
trotzdem konstruiert werden.Option 3)
boost::variant<unsigned int, StructType> read_struct( const std::string& file_name )
Die Auswertung des Rückgabewerts ist iwie sperrig. Ist im Moment trotzdem mein Favorit.Option 4)
?Gruß,
Doc
-
boost::optional<StructType> read_struct( const std::string& file_name )
falls du nur "ging nicht" benötigst.
-
@manni66
Ne, leider nicht. Wenn´s nicht geht muss der Benutzer erfahren, warum´s nicht ging.
-
Dann würde ich Option 2 mit boost::optional nehmen:
std::tuple<unsigned int, boost::optional<StructType>> read_struct( const std::string& file_name )
Statt
tuple
könnte man auch einestruct
definieren.
-
Ich persönlich tendiere zu 1.
Das Problem mit den Daten kann man auch einfach mit einem shared_ptr lösen...
-
@manni66
Hast du ne Begründung? Oder ist das Bauchgefühl?
-
Mal doof gefragt: Gute, alte Exceptions sind keine Option?
Die könnte man z.B. bequem nur dort behandeln, wo man den Benutzer informieren will, z.B. in einem "Datei laden"-Dialog - ohne den Status quer durch den ganzen Callstack reichen zu müssen.
Auch eine Hybrid-Variante aus
optional
und Exceptions wäre möglich - siehe u.a. Andrei Alexandrescusexpected<T>
.Kann natürlich sein, dass das Lowlevel-Code werden soll, der auch in
-fno-exceptions
-Umgebungen zur Anwendung kommen soll. Da kann man natürlich mitoptional
und Status-Codes arbeiten.
-
- es gibt immer einen Errorcode den man testen kann (wie in Option 1)
- ich brauche keinen künstlichen "Leer"-Wert für StructType
- das Ergebnis kommt insgesamt als Return, es muss nicht vorher etwas besorgt werden
Mit C++ 17 würde ich es so benutzen:
#include <optional> #include <tuple> #include <iostream> std::tuple<int, std::optional<int>> doit() { return { 0, std::optional<int>{19} }; } int main() { if( auto [ err, val ] = doit(); err ) { std::cerr << "Fehler: " << err << "\n"; } else { std::cout << *val << "\n"; } }
-
std::expected:
https://www.youtube.com/watch?v=CGwk3i1bGQI
https://github.com/TartanLlama/expected// edit: wurde schon erwähnt
-
Weil noch ein Rückgabeparameter dazugekommen ist habe ich mich für eine Mischung aus 2) und 4) entschieden:
struct ResultXYZ { StatusType::Type_t Status; unsigned int ErrorCode; boost::optional<XYZ> Data; explicit ResultXYZ( unsigned int error_code ); // Status kann aus ErrorCode abgeleitet werden explicit ResultXYZ( XYZ data ); // wird nach Data gemoved, Status und ErrorCode können abgeleitet werden };
Danke für eure Vorschläge.
-
@Finnegan
Ich könnte schon mischen, dann wird´s aber noch unübersichtlicher. Es gäbe dann drei Fälle:- Datei kann gelesen werden -> alles ok
- Datei kann nicht gelesen werden, weil durch anderen Prozess gelockt -> Kein Fehler, keine Exception, Fehlercode?
- Sonstiger Fehler, der nicht vorgesehen ist -> Exception werfen
Obwohl... jetzt wo ich so schreibe...
Ich könnte das vllt über boost::optional erschlagen. Im Fall 1)StructType
zurückgeben, im Fall 2)boost::none
und ansonsten eine Exception werfen. Ich werd´ da noch mal in Ruhe drüber nachdenken.
-
@DocShoe sagte in Designfrage Rückgabeparameter:
@Finnegan
Ich könnte schon mischen, dann wird´s aber noch unübersichtlicher. Es gäbe dann drei Fälle:- Datei kann gelesen werden -> alles ok
- Datei kann nicht gelesen werden, weil durch anderen Prozess gelockt -> Kein Fehler, keine Exception, Fehlercode?
- Sonstiger Fehler, der nicht vorgesehen ist -> Exception werfen
Genau so mache ich das meistens. Also wenn es genau einen "erlaubten" Fall gibt wo nix zurückkommt, dann nenne ich die Funktion "tryXxx" oder "tryGetXxx" und im erlaubten Fehlerfall kommt halt nix zurück. Verträgt sich auch gut mit diversen anderen möglichen Problemen die sowieso schon per Exception kommuniziert würden, z.B. out of memory -> {{bad_alloc}}.
Geht aber natürlich nur wenn es nur "den" erlaubten Fehlergrund gibt, bzw. wenn die genauen Details einfach nicht wichtig sind.
-
@DocShoe sagte in Designfrage Rückgabeparameter:
@Finnegan
Ich könnte schon mischen, dann wird´s aber noch unübersichtlicher. Es gäbe dann drei Fälle:- Datei kann gelesen werden -> alles ok
- Datei kann nicht gelesen werden, weil durch anderen Prozess gelockt -> Kein Fehler, keine Exception, Fehlercode?
- Sonstiger Fehler, der nicht vorgesehen ist -> Exception werfen
Obwohl... jetzt wo ich so schreibe...
Ich könnte das vllt über boost::optional erschlagen. Im Fall 1)StructType
zurückgeben, im Fall 2)boost::none
und ansonsten eine Exception werfen. Ich werd´ da noch mal in Ruhe drüber nachdenken.Das klingt alles so, als könnte
expected
die Lösung sein, die du suchst.expected
ist eine Art Kombination ausoptional
undvariant
. Es hält entweder den erwarteten Rückgabewert (in deinem FallStructType
) oder eine noch nicht geworfene (!) Exception, die Informationen darüber beinhaltet, weshalb kein erwarteter Rückgabewert vorliegt.expected
bietet die Möglichkeit, den Fehler entweder lokal zu handhaben, wie bei einem klassischen Fehlercode (Dein Punkt 2):expected<StructType, ResultXYZ> read_struct(const string& file_name); ... auto result = read_struct(file_name); if (!result && result.error().ErrorCode == DATEI_IST_GELOCKT) { std::cerr << "Datei ist gelockt :(" << std::endl; ... }
... oder sich nicht um den Fehler zu scheren und einfach mit dem erwarteten Rückgabewert zu arbeiten (Punkt 3):
auto result = read_struct(file_name); ... mach_was_mit_struct(result.value());
In diesem Fall wirft dann
expected
die hinterlegte Exception vom TypResultXYZ
, falls kein Wert vorliegt.ResultXYZ
habe ich hier jetzt einfach mal genommen, weil es dem Fehlerfall/Exception-Typ, den du wahrscheinlich fürexcpected
haben wollen würdest am nächsten kommt. OhneData
-Member natürlich und evtl. noch vonstd::exception
abgeleitet.Leider ist
expected
noch nicht Teil der Standardbibliothek. Du müsstest also auf die von theta gepostete Implementierung zurückgreifen, oder es selbst schreiben. Beim stöbern bin ich auch noch überboost.outcome
gestolpert gestolpert. Das scheint etwas sehr ähnliches, wenn nicht sogar das gleiche zu sein.
-
Sind
expected
und/oderoutcome
überhaupt schon fertig ge-bike-shedded?
Ich hab da zumindest vor kurzem noch elendslange Diskussionen in der Boost Mailingliste zu dem Thema gesehen. Das blöde bei der Sache ist halt, dass es einfach klingt, aber leider überhaupt nicht einfach ist. Zumindest nicht wenn es flexibel und akzeptabel einfach zu verwenden sein soll und performant implementierbar.Von daher würde ich eher bei
optional<Result> tryDoTheThing(params); // und/oder optional<Result> tryDoTheThing(params, ErrorInfo& out_errorInfo);
bleiben.
Bzw. alternativ auch
bool tryDoTheThing(params, Result& out_result); // und/oder ErrorCode tryDoTheThing(params, Result& out_result);
Ich persönlich werde auch jeden Fall bei
expected
undoutcome
nicht early-adoptor spielen.
-
@hustbaer sagte in Designfrage Rückgabeparameter:
Sind
expected
und/oderoutcome
überhaupt schon fertig ge-bike-shedded?Da ist was dran. Dass das noch gar nicht so ausgereift ist, war mir gar nicht bekannt, auf dem Papier sieht es jedenfalls toll aus. Danke für den Hinweis, das ist wohl erstmal nur für Projekte geeignet, bei denen man sich Experimente erlauben kann.
Dennoch die m.E. "schönste" Lösung - wenn sie denn standardisiert wäre
-
@Finnegan Naja die Frage war schon ernst gemeint, ich weiss nicht ob die schon "final" sind bzw. in einem "verlässlich" brauchbarem Zustand. Kann sein, kann sein nicht. Ich weiss nur dass auf der Boost Mailing Liste vor wenigen Monaten noch lebhaft über das Design diskutiert wurde. @Columbo weiss da vermutlich mehr
-
@hustbaer sagte in Designfrage Rückgabeparameter:
@Finnegan Naja die Frage war schon ernst gemeint, ich weiss nicht ob die schon "final" sind bzw. in einem "verlässlich" brauchbarem Zustand. Kann sein, kann sein nicht. Ich weiss nur dass auf der Boost Mailing Liste vor wenigen Monaten noch lebhaft über das Design diskutiert wurde. @Columbo weiss da vermutlich mehr
Nun, zumindest laut der Outcome-Website ist die Bibliothek wohl gerade offiziell in Boost aufgenommen worden:
This library joined the Boost C++ libraries in the 1.70 release (Spring 2019).
1.70 ist das kommende Release. Mehr Hintergründe habe ich nicht, aber es lässt vermuten, dass wohl ein Großteil der in den Mailinglisten angeprochenen Probleme behoben wurde. "Battle tested" kann man das aber wohl noch nicht nennen, deutlich ausgereifter als etwas "mal eben selbst gestricktes" dürfte es aber dennoch sein.
Auch gibt es ein paar Unterschiede zum vorgeschlagenen
std::expected
.
-
Naja wenn das ganze in den Standard wandern soll ... ich glaub ich warte dann einfach bis es im Standard ist
Ansonsten warte ich auch immer gerne bis Dinge 2-3 Jahre von den Early Adoptern verwendet wurden. Dann sollten die meisten Kinderkrankheiten behoben sein, bzw. wenn sich herausstellt dass es vom Grundkonzept her in der Praxis eigentlich gar nicht so gut funktioniert wie erhofft, dann lass ich es lieber ganz.
-
Wow, schicke Diskussion!
Werde mich in outcome und expected mal einlesen, obwohl ich das wohl nie einsetzen können werde (Ich fürchte, wir bleiben beim Embarcadero RAD Studio 10.1 und dem clang 3.2. Der unterstützt nur C++11 und eine Portierung der boost Bibliotheken ist zu aufwändig. expected wird vom clang 3.2 auch nicht unterstützt, also sind beide raus).