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 Funktion Delegate *App::get_delegate() auf einer App Instanz Delegate *App::get_delegate() , die keinen Delegaten hatte, würde nullptr .
    • 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.
  • 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 undefined int und einem bool auf false .

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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow