C++
std :: optionele
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 eenApp
exemplaar zonder delegate zounullptr
. - 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.
- bijv. Het aanroepen van een functie
- 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.
- bijv. Het aanroepen van een functie
- 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 ongedefinieerdeint
en eenbool
opfalse
teruggeven.
- bijv. Het aanroepen van een functie
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.