Zoeken…


Invoering

Optionals (ook bekend als Misschien-typen) worden gebruikt om een type weer te geven waarvan de inhoud al dan niet aanwezig is. Ze zijn geïmplementeerd in C ++ 17 als de std::optional klasse. Een object van het type std::optional<int> kan bijvoorbeeld een waarde van het type int , of het kan geen waarde bevatten.

Optionele opties worden gewoonlijk gebruikt om een waarde weer te geven die mogelijk niet bestaat of als een retourtype van een functie die een zinvol resultaat niet kan retourneren.

Andere benaderingen van optioneel

Er zijn veel andere manieren om het probleem op te lossen dat std::optional oplost, maar geen daarvan is vrij compleet: een aanwijzer gebruiken, een schildwacht gebruiken of een pair<bool, T> .

Optioneel versus aanwijzer

In sommige gevallen kunnen we een aanwijzer naar een bestaand object of nullptr om een storing aan te geven. Maar dit is beperkt tot die gevallen waar objecten al bestaan - optional , als een waardetype, kan ook worden gebruikt om nieuwe objecten te retourneren zonder toevlucht te nemen tot geheugentoewijzing.

Optioneel versus Sentinel

Een gebruikelijk idioom is om een speciale waarde te gebruiken om aan te geven dat de waarde betekenisloos is. Dit kan 0 of -1 zijn voor integrale typen, of nullptr voor pointers. Dit verkleint echter de ruimte met geldige waarden (u kunt geen onderscheid maken tussen een geldige 0 en een betekenisloze 0) en veel typen hebben geen natuurlijke keuze voor de schildwachtwaarde.

Optioneel versus std::pair<bool, T>

Een ander veel voorkomend idioom is om een paar te geven, waarbij een van de elementen een bool aangeeft of de waarde al dan niet betekenisvol is.

Dit is afhankelijk van het feit dat het waardetype standaard kan worden geconstrueerd in het geval van fouten, wat voor sommige typen niet mogelijk is en voor andere onwenselijk. Een optional<T> hoeft in het geval van een fout niets te construeren.

Optionals gebruiken om de afwezigheid van een waarde weer te geven

Vóór C ++ 17 vertegenwoordigde het hebben van pointers met een waarde van nullptr meestal de afwezigheid van een waarde. Dit is een goede oplossing voor grote objecten die dynamisch zijn toegewezen en al worden beheerd door pointers. Deze oplossing werkt echter niet goed voor kleine of primitieve typen zoals int , die zelden dynamisch worden toegewezen of beheerd door pointers. std::optional biedt een haalbare oplossing voor dit veelvoorkomende probleem.

In dit voorbeeld is struct Person gedefinieerd. Het is mogelijk voor een persoon om een huisdier te hebben, maar niet noodzakelijk. Daarom wordt het pet van Person verklaard met een std::optional wrapper.

#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;
    }
}

Optionals gebruiken om het falen van een functie weer te geven

Vóór C ++ 17 vertegenwoordigde een functie meestal een fout op een van de volgende manieren:

  • Een lege wijzer werd teruggegeven.
    • bijv. Het aanroepen van een functie Delegate *App::get_delegate() op een App exemplaar zonder delegate zou nullptr .
    • Dit is een goede oplossing voor objecten die dynamisch zijn toegewezen of groot zijn en worden beheerd door pointers, maar is geen goede oplossing voor kleine objecten die meestal worden toegewezen aan een stapel en worden gekopieerd.
  • Een specifieke waarde van het retourtype was gereserveerd om een storing aan te geven.
    • bijv. Het aanroepen van een functie unsigned shortest_path_distance(Vertex a, Vertex b) op twee niet-verbonden hoekpunten kan nul retourneren om dit feit aan te geven.
  • De waarde is gekoppeld aan een bool om aan te geven dat de geretourneerde waarde zinvol was.
    • bijv. Het aanroepen van een functie std::pair<int, bool> parse(const std::string &str) met een stringargument dat geen geheel getal is, zou een paar met een ongedefinieerde int en een bool op false teruggeven.

In dit voorbeeld krijgt John twee huisdieren, Fluffy en Furball. De functie Person::pet_with_name() wordt vervolgens opgeroepen om John's huisdier Whiskers op te halen. Omdat John geen huisdier met de naam Whiskers heeft, mislukt de functie en wordt in plaats daarvan std::nullopt geretourneerd.

#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;
    }
}

optioneel als retourwaarde

std::optional<float> divide(float a, float b) {
  if (b!=0.f) return a/b;
  return {};
}

Hier retourneren we de breuk a/b , maar als deze niet is gedefinieerd (zou oneindig zijn) retourneren we in plaats daarvan de lege optie.

Een meer complexe zaak:

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 ) doorzoekt de container of bereik some_range voor iets gelijk aan het getal 7 . find_if doet het met een predikaat.

Het retourneert een lege optionele als het niet werd gevonden, of een optionele met een iterator voor het element als het was.

Hiermee kunt u het volgende doen:

if (find( vec, 7 )) {
  // code
}

of zelfs

if (auto oit = find( vec, 7 )) {
  vec.erase(*oit);
}

zonder te hoeven prutsen met begin / einde iterators en tests.

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 retourneert de waarde die is opgeslagen in het optionele, of het argument als er niets is opgeslagen.

Hiermee kunt u de misschien-nul-optie nemen en een standaardgedrag geven wanneer u daadwerkelijk een waarde nodig hebt. Door het op deze manier te doen, kan de beslissing "standaardgedrag" teruggeduwd worden naar het punt waar het het beste is en onmiddellijk nodig is, in plaats van een standaardwaarde diep in de ingewanden van een motor te genereren.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow