Ich lese die Dokumentation von std::experimental::optional
und habe eine gute Vorstellung davon, was es tut, aber ich verstehe nicht, wann ich es verwenden soll oder wie ich es verwenden soll. Die Seite enthält noch keine Beispiele, was es mir schwerer macht, das wahre Konzept dieses Objekts zu verstehen. Wann ist std::optional
eine gute Wahl und wie wird das kompensiert, was im vorherigen Standard (C ++ 11) nicht gefunden wurde?
c++
boost-optional
c++-tr2
0x499602D2
quelle
quelle
optional
. Stellen Sie sich vor, Sie wollen einoptional<int>
oder sogar<char>
. Glauben Sie wirklich, dass es "Zen" ist, dynamisch zuzuweisen, zu dereferenzieren und dann zu löschen - etwas, das sonst keine Zuordnung erfordern könnte und kompakt auf den Stapel und möglicherweise sogar in ein Register passt?std::optional
(solange es sein kannstd::unique_ptr
) kein unvollständiger Typ sein kann . Genauer gesagt verlangt die Norm, dass T [...] die Anforderungen von Destructible erfüllt .Antworten:
Das einfachste Beispiel, das ich mir vorstellen kann:
Dasselbe kann stattdessen mit einem Referenzargument erreicht werden (wie in der folgenden Signatur), aber die Verwendung
std::optional
macht die Signatur und Verwendung angenehmer.Eine andere Möglichkeit, dies zu tun, ist besonders schlecht :
Dies erfordert eine dynamische Speicherzuweisung, Bedenken hinsichtlich des Eigentums usw. - bevorzugen Sie immer eine der beiden anderen oben genannten Signaturen.
Ein anderes Beispiel:
Dies ist extrem vorzuziehen, anstatt
std::unique_ptr<std::string>
für jede Telefonnummer so etwas wie eine zu haben!std::optional
bietet Ihnen Datenlokalität, was sich positiv auf die Leistung auswirkt.Ein anderes Beispiel:
Wenn die Suche keinen bestimmten Schlüssel enthält, können wir einfach "kein Wert" zurückgeben.
Ich kann es so benutzen:
Ein anderes Beispiel:
Dies ist viel sinnvoller als beispielsweise vier Funktionsüberladungen, die jede mögliche Kombination von
max_count
(oder nicht) undmin_match_score
(oder nicht) erfordern!Es beseitigt auch den verfluchten "Pass
-1
für,max_count
wenn Sie kein Limit wollen" oder "Passstd::numeric_limits<double>::min()
für,min_match_score
wenn Sie keine Mindestpunktzahl wollen"!Ein anderes Beispiel:
Wenn die Abfragezeichenfolge nicht vorhanden ist
s
, möchte ich "Neinint
" - nicht den speziellen Wert, den jemand für diesen Zweck verwendet hat (-1?).Weitere Beispiele finden Sie in der
boost::optional
Dokumentation .boost::optional
undstd::optional
wird grundsätzlich in Bezug auf Verhalten und Nutzung identisch sein.quelle
std::optional<T>
ist nur einT
und einbool
. Die Implementierungen der Mitgliedsfunktionen sind äußerst einfach. Die Leistung sollte bei der Verwendung eigentlich kein Problem sein - manchmal ist etwas optional. In diesem Fall ist dies häufig das richtige Werkzeug für den Job.std::optional<T>
ist viel komplexer als das. Es verwendet Platzierungnew
und viele andere Dinge mit der richtigen Ausrichtung und Größe, um es unter anderem zu einem wörtlichen Typ (dh Verwendung mitconstexpr
) zu machen. Die NaivitätT
undbool
Herangehensweise würde ziemlich schnell scheitern.union storage_t { unsigned char dummy_; T value_; ... }
Zeile 289:struct optional_base { bool init_; storage_t<T> storage_; ... }
Wie ist das nicht "aT
und abool
"? Ich stimme voll und ganz zu, dass die Implementierung sehr knifflig und nicht trivial ist, aber konzeptionell und konkret ist der Typ aT
und abool
. "Die NaivitätT
undbool
Herangehensweise würde ziemlich schnell scheitern." Wie können Sie diese Aussage machen, wenn Sie sich den Code ansehen?struct{bool,maybe_t<T>}
die Gewerkschaft, die einfach da ist, um nicht zu tun,struct{bool,T}
was in allen Fällen ein T konstruieren würde.std::unique_ptr<T>
undstd::optional<T>
in gewissem Sinne erfüllen die Rolle eines "optionalenT
". Ich würde den Unterschied zwischen ihnen als "Implementierungsdetails" beschreiben: zusätzliche Zuweisungen, Speicherverwaltung, Datenlokalität, Umzugskosten usw. Ich hätte esstd::unique_ptr<int> try_parse_int(std::string s);
zum Beispiel nie getan , weil dies eine Zuweisung für jeden Anruf verursachen würde, obwohl es keinen Grund dafür gibt . Ich würde niemals eine Klasse mit einem habenstd::unique_ptr<double> limit;
- warum eine Zuordnung vornehmen und Datenlokalität verlieren?Ein Beispiel wird aus dem neu angenommenen Papier zitiert : N3672, std :: optional :
quelle
int
, von dem angenommen wird, dass er eine "Fehler" -Bedeutung hat.str2int()
die Konvertierung implementieren, wie es will, (B) unabhängig von der Methode, um die zu erhaltenstring s
, und (C) vermittelt die volle Bedeutung über dieoptional<int>
anstelle einer dummen magischen Zahl,bool
/ Referenz oder dynamischen Zuweisungsmethode es tun.Überlegen Sie, wann Sie eine API schreiben und ausdrücken möchten, dass der Wert "Keine Rückgabe" kein Fehler ist. Sie müssen beispielsweise Daten aus einem Socket lesen. Wenn ein Datenblock vollständig ist, analysieren Sie ihn und geben ihn zurück:
Wenn die angehängten Daten einen analysierbaren Block abgeschlossen haben, können Sie ihn verarbeiten. Andernfalls lesen Sie die Daten weiter und hängen Sie sie an:
Bearbeiten: in Bezug auf den Rest Ihrer Fragen:
Wenn Sie einen Wert berechnen und ihn zurückgeben müssen, ist es besser, die Semantik nach Wert zurückzugeben, als auf einen Ausgabewert zu verweisen (der möglicherweise nicht generiert wird).
Wenn Sie diesen Client - Code sicherstellen wollen , hat den Ausgangswert zu überprüfen (wer auch immer den Client - Code schreibt für Fehler kann nicht überprüfen - wenn Sie einen nicht initialisierten Zeiger Sie einen Core Dump erhalten versuchen , zu verwenden; wenn Sie eine unbe verwenden versuchen , initialisiert std :: optional, Sie erhalten eine abfangbare Ausnahme).
Vor C ++ 11 mussten Sie eine andere Schnittstelle für "Funktionen, die möglicherweise keinen Wert zurückgeben" verwenden - entweder per Zeiger zurückgeben und auf NULL prüfen oder einen Ausgabeparameter akzeptieren und einen Fehler- / Ergebniscode für "nicht verfügbar" zurückgeben ".
Beide erfordern vom Client-Implementierer zusätzlichen Aufwand und Aufmerksamkeit, um es richtig zu machen, und beide sorgen für Verwirrung (der erste veranlasst den Client-Implementierer, eine Operation als Zuweisung zu betrachten, und erfordert, dass der Client-Code die Zeigerbehandlungslogik implementiert, und der zweite erlaubt Client-Code, um mit ungültigen / nicht initialisierten Werten davonzukommen).
std::optional
kümmert sich gut um die Probleme, die bei früheren Lösungen auftreten.quelle
boost::
stattstd::
?boost::optional
weil es nach ungefähr zwei Jahren der Verwendung in meinem präfrontalen Kortex fest codiert ist).Ich verwende häufig Optionals, um optionale Daten darzustellen, die aus Konfigurationsdateien abgerufen wurden, dh wo diese Daten (z. B. mit einem erwarteten, aber nicht erforderlichen Element in einem XML-Dokument) optional bereitgestellt werden, damit ich explizit und klar zeigen kann, ob Die Daten waren tatsächlich im XML-Dokument vorhanden. Insbesondere wenn die Daten einen "nicht gesetzten" Zustand gegenüber einem "leeren" und einem "gesetzten" Zustand haben können (Fuzzy-Logik). Mit einem optionalen, gesetzt und nicht gesetzt ist klar, wäre auch leer mit dem Wert 0 oder null klar.
Dies kann zeigen, dass der Wert von "nicht gesetzt" nicht gleich "leer" ist. Im Konzept kann ein Zeiger auf ein int (int * p) dies zeigen, wenn keine Null (p == 0) gesetzt ist, ein Wert von 0 (* p == 0) gesetzt und leer ist und jeder andere Wert (* p <> 0) wird auf einen Wert gesetzt.
Als praktisches Beispiel habe ich ein Stück Geometrie aus einem XML-Dokument gezogen, das einen Wert namens Renderflags hatte, wobei die Geometrie entweder die Renderflags überschreiben (setzen), die Renderflags deaktivieren (auf 0 setzen) oder einfach nicht kann Auswirkungen auf die Render-Flags (nicht gesetzt), wäre eine Option eine klare Möglichkeit, dies darzustellen.
Natürlich kann ein Zeiger auf ein int in diesem Beispiel das Ziel erreichen, oder besser ein Freigabezeiger, da er eine sauberere Implementierung bieten kann. Ich würde jedoch argumentieren, dass es in diesem Fall um Codeklarheit geht. Ist eine Null immer ein "nicht gesetzt"? Mit einem Zeiger ist dies nicht klar, da null wörtlich bedeutet, dass es nicht zugewiesen oder erstellt wurde, obwohl dies möglich , aber möglicherweise nicht unbedingt erforderlich ist mean „nicht festgelegt“. Es ist darauf hinzuweisen, dass ein Zeiger freigegeben und in der guten Praxis auf 0 gesetzt werden muss. Wie bei einem gemeinsam genutzten Zeiger erfordert ein optionales Element jedoch keine explizite Bereinigung, sodass keine Bedenken bestehen, die Bereinigung mit zu verwechseln das optionale wurde nicht eingestellt.
Ich glaube, es geht um Code-Klarheit. Klarheit reduziert die Kosten für die Wartung und Entwicklung von Code. Ein klares Verständnis der Code-Absicht ist unglaublich wertvoll.
Die Verwendung eines Zeigers, um dies darzustellen, würde das Überladen des Konzepts des Zeigers erfordern. Um "null" als "nicht gesetzt" darzustellen, werden normalerweise ein oder mehrere Kommentare durch Code angezeigt, um diese Absicht zu erklären. Das ist keine schlechte Lösung anstelle einer optionalen. Ich entscheide mich jedoch immer für eine implizite Implementierung und nicht für explizite Kommentare, da Kommentare nicht durchsetzbar sind (z. B. durch Kompilierung). Beispiele für diese impliziten Elemente für die Entwicklung (jene Artikel in der Entwicklung, die nur zur Durchsetzung von Absichten bereitgestellt werden) sind die verschiedenen C ++ - Casts "const" (insbesondere für Elementfunktionen) und der Typ "bool", um nur einige zu nennen. Wahrscheinlich brauchen Sie diese Codefunktionen nicht wirklich, solange jeder Absichten oder Kommentaren gehorcht.
quelle