C++
std :: optional
Suche…
Einführung
Optionale (auch als Vielleicht-Typen bezeichnet) werden verwendet, um einen Typ darzustellen, dessen Inhalt möglicherweise vorhanden ist oder nicht. Sie sind in C ++ 17 als std::optional
Klasse std::optional
implementiert. Ein Objekt des Typs std::optional<int>
kann beispielsweise einen Wert des Typs int
enthalten oder keinen Wert.
Optionals werden häufig verwendet, um entweder einen Wert darzustellen, der möglicherweise nicht vorhanden ist, oder als Rückgabetyp einer Funktion, die möglicherweise kein aussagekräftiges Ergebnis liefert.
Andere Ansätze für optional
Es gibt viele andere Ansätze zur Lösung des Problems, die von std::optional
gelöst werden, aber keines ist vollständig: Verwenden eines Zeigers, eines Sentinels oder eines pair<bool, T>
.
Optional gegen Zeiger
In einigen Fällen können wir einen Zeiger auf ein vorhandenes Objekt oder einen nullptr
angeben, um einen nullptr
anzuzeigen. Dies ist jedoch auf Fälle beschränkt, in denen Objekte bereits vorhanden sind. optional
kann als Werttyp auch verwendet werden, um neue Objekte zurückzugeben, ohne auf die Speicherzuordnung zurückzugreifen.
Optional gegen Sentinel
Ein üblicher Ausdruck ist die Verwendung eines speziellen Werts, um anzuzeigen, dass der Wert bedeutungslos ist. Dies kann 0 oder -1 für ganzzahlige Typen sein oder nullptr
für Zeiger. Dies reduziert jedoch den Platz für gültige Werte (Sie können nicht zwischen einer gültigen 0 und einer sinnlosen 0 unterscheiden) und viele Typen haben keine natürliche Wahl für den Sentinel-Wert.
Optional vs std::pair<bool, T>
Eine andere verbreitete Redewendung besteht darin, ein Paar bereitzustellen, bei dem eines der Elemente ein bool
angibt, ob der Wert sinnvoll ist oder nicht.
Dies setzt voraus, dass der Wertetyp im Fehlerfall standardmäßig aufbaubar ist, was für einige Typen nicht möglich und für andere jedoch unerwünscht ist. Ein optional<T>
muss im Fehlerfall nichts konstruieren.
Verwenden von Optionals, um das Fehlen eines Werts darzustellen
Vor C ++ 17 stellte das nullptr
von Zeigern mit einem Wert von nullptr
üblicherweise das Fehlen eines Werts dar. Dies ist eine gute Lösung für große Objekte, die dynamisch zugewiesen wurden und bereits von Zeigern verwaltet werden. Diese Lösung funktioniert jedoch nicht gut für kleine oder primitive Typen wie int
, die selten dynamisch durch Zeiger zugewiesen oder verwaltet werden. std::optional
bietet eine praktikable Lösung für dieses häufige Problem.
In diesem Beispiel wird struct Person
definiert. Es ist möglich, dass eine Person ein Haustier hat, dies ist jedoch nicht erforderlich. Daher wird das pet
Mitglied von Person
mit einem std::optional
Wrapper std::optional
deklariert.
#include <iostream>
#include <optional>
#include <string>
struct Animal {
std::string name;
};
struct Person {
std::string name;
std::optional<Animal> pet;
};
int main() {
Person person;
person.name = "John";
if (person.pet) {
std::cout << person.name << "'s pet's name is " <<
person.pet->name << std::endl;
}
else {
std::cout << person.name << " is alone." << std::endl;
}
}
Verwendung von Optionals zur Darstellung des Fehlers einer Funktion
Vor C ++ 17 stellte eine Funktion normalerweise einen Fehler auf verschiedene Arten dar:
- Ein Nullzeiger wurde zurückgegeben.
-
Delegate *App::get_delegate()
beispielsweise eine FunktionDelegate *App::get_delegate()
auf einerApp
InstanzDelegate *App::get_delegate()
, die keinen Delegaten hatte, würdenullptr
. - Dies ist eine gute Lösung für Objekte, die dynamisch zugewiesen wurden oder groß sind und von Zeigern verwaltet werden, ist jedoch keine gute Lösung für kleine Objekte, die in der Regel stapelweise zugewiesen und durch Kopieren übergeben werden.
-
- Ein bestimmter Wert des Rückgabetyps wurde reserviert, um einen Fehler anzuzeigen.
- Das Aufrufen einer Funktion
unsigned shortest_path_distance(Vertex a, Vertex b)
auf zwei nicht verbundenen Scheitelpunkten kann den Wert Null zurückgeben, um diese Tatsache anzuzeigen.
- Das Aufrufen einer Funktion
- Der Wert wurde mit einem
bool
gepaart, um anzuzeigen, dass der zurückgegebene Wert sinnvoll ist.- Ein Aufruf einer Funktion
std::pair<int, bool> parse(const std::string &str)
mit einem String-Argument, das keine Ganzzahl ist, würde ein Paar mit einem undefinedint
und einembool
auffalse
.
- Ein Aufruf einer Funktion
In diesem Beispiel erhält John zwei Haustiere, Fluffy und Furball. Die Funktion Person::pet_with_name()
wird dann aufgerufen, um Johns Whiskers abzurufen. Da John kein Haustier mit dem Namen Whiskers hat, schlägt die Funktion fehl und std::nullopt
wird std::nullopt
zurückgegeben.
#include <iostream>
#include <optional>
#include <string>
#include <vector>
struct Animal {
std::string name;
};
struct Person {
std::string name;
std::vector<Animal> pets;
std::optional<Animal> pet_with_name(const std::string &name) {
for (const Animal &pet : pets) {
if (pet.name == name) {
return pet;
}
}
return std::nullopt;
}
};
int main() {
Person john;
john.name = "John";
Animal fluffy;
fluffy.name = "Fluffy";
john.pets.push_back(fluffy);
Animal furball;
furball.name = "Furball";
john.pets.push_back(furball);
std::optional<Animal> whiskers = john.pet_with_name("Whiskers");
if (whiskers) {
std::cout << "John has a pet named Whiskers." << std::endl;
}
else {
std::cout << "Whiskers must not belong to John." << std::endl;
}
}
optional als Rückgabewert
std::optional<float> divide(float a, float b) {
if (b!=0.f) return a/b;
return {};
}
Hier geben wir entweder den Bruch a/b
, aber wenn es nicht definiert ist (wäre unendlich), geben wir stattdessen das leere optional zurück.
Ein komplexerer Fall:
template<class Range, class Pred>
auto find_if( Range&& r, Pred&& p ) {
using std::begin; using std::end;
auto b = begin(r), e = end(r);
auto r = std::find_if(b, e , p );
using iterator = decltype(r);
if (r==e)
return std::optional<iterator>();
return std::optional<iterator>(r);
}
template<class Range, class T>
auto find( Range&& r, T const& t ) {
return find_if( std::forward<Range>(r), [&t](auto&& x){return x==t;} );
}
find( some_range, 7 )
durchsucht den Container oder den Bereich some_range
nach etwas, das der Nummer 7
. find_if
tut es mit einem Prädikat.
Es wird entweder ein leeres optionales Element zurückgegeben, wenn es nicht gefunden wurde, oder ein optionales Element, das einen Iterator für das Element enthält, falls es gefunden wurde.
Dies ermöglicht Ihnen Folgendes:
if (find( vec, 7 )) {
// code
}
oder auch
if (auto oit = find( vec, 7 )) {
vec.erase(*oit);
}
ohne mit Anfang / Ende Iteratoren und Tests herumspielen zu müssen.
value_or
void print_name( std::ostream& os, std::optional<std::string> const& name ) {
std::cout "Name is: " << name.value_or("<name missing>") << '\n';
}
value_or
entweder den im optionalen Wert gespeicherten Wert zurück oder das Argument, wenn dort nichts gespeichert ist.
Auf diese Weise können Sie die Option vielleicht-null verwenden und ein Standardverhalten angeben, wenn Sie tatsächlich einen Wert benötigen. Auf diese Weise kann die Entscheidung "Standardverhalten" an den Punkt zurückgedrängt werden, an dem sie am besten getroffen wird und sofort benötigt wird, anstatt einen Standardwert zu erzeugen, der tief im Innern einer Engine liegt.