C++
std :: valfritt
Sök…
Introduktion
Alternativ (även känd som kanske typer) används för att representera en typ vars innehåll eventuellt finns eller inte finns. De implementeras i C ++ 17 som std::optional
klass. Till exempel kan ett objekt av typen std::optional<int>
innehålla något värde av typen int
, eller det kan inte innehålla något värde.
Alternativ används ofta för att representera ett värde som kanske inte finns eller som en returtyp från en funktion som kan misslyckas med att returnera ett meningsfullt resultat.
Andra tillvägagångssätt för valfritt
Det finns många andra metoder för att lösa problemet som std::optional
löser, men ingen av dem är ganska fullständiga: att använda en pekare, använda en vaktpost eller använda ett pair<bool, T>
.
Valfritt vs Pointer
I vissa fall kan vi tillhandahålla en pekare till ett befintligt objekt eller nullptr
att indikera fel. Men detta är begränsat till de fall där objekt redan finns - optional
, som en värdetyp, kan också användas för att returnera nya objekt utan att ta till minnesallokering.
Valfritt vs Sentinel
Ett vanligt formspråk är att använda ett specialvärde för att indikera att värdet är meningslöst. Detta kan vara 0 eller -1 för integrerade typer, eller nullptr
för pekare. Detta minskar emellertid utrymmet för giltiga värden (du kan inte skilja mellan ett giltigt 0 och ett meningslöst 0) och många typer har inte ett naturligt val för vaktvärden.
Valfritt vs std::pair<bool, T>
Ett annat vanligt formspråk är att tillhandahålla ett par, där ett av elementen är en bool
anger om värdet är meningsfullt eller inte.
Detta förlitar sig på att värdetypen är standardkonstruerbar i händelse av fel, vilket inte är möjligt för vissa typer och möjligt men oönskat för andra. En optional<T>
, i händelse av fel, behöver inte konstruera någonting.
Använda tillval för att representera frånvaron av ett värde
Före C ++ 17 nullptr
vanligtvis frånvaron av ett värde med att ha pekare med ett värde på nullptr
. Detta är en bra lösning för stora objekt som har tilldelats dynamiskt och som redan hanteras av pekare. Men den här lösningen fungerar inte bra för små eller primitiva typer som int
, som sällan någonsin dynamiskt tilldelas eller hanteras av pekare. std::optional
ger en livskraftig lösning på detta vanliga problem.
I det här exemplet definieras struct Person
. Det är möjligt för en person att ha ett husdjur, men inte nödvändigt. Därför förklaras pet
till Person
med en std::optional
omslag.
#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;
}
}
Använda tillval för att representera en funktionsfel
Före C ++ 17 representerade en funktion vanligtvis fel på ett av flera sätt:
- En nollpekare returnerades.
- t.ex. att ringa en funktion
Delegate *App::get_delegate()
på enApp
instans som inte hade en delegat skulle returneranullptr
. - Detta är en bra lösning för objekt som har tilldelats dynamiskt eller som är stora och hanteras av pekare, men är inte en bra lösning för små objekt som vanligtvis staplas tilldelas och skickas genom kopiering.
- t.ex. att ringa en funktion
- Ett specifikt värde för returtypen reserverades för att indikera fel.
- t.ex. att ringa en funktion som inte är
unsigned shortest_path_distance(Vertex a, Vertex b)
på två hörn som inte är anslutna kan komma tillbaka noll för att indikera detta.
- t.ex. att ringa en funktion som inte är
- Värdet kopplades ihop med en
bool
att indikera att det returnerade värdet var meningsfullt.- t.ex. att ringa en funktion
std::pair<int, bool> parse(const std::string &str)
med ett strängargument som inte är ett heltal skulle returnera ett par med en odefinieradint
och enbool
som ärfalse
.
- t.ex. att ringa en funktion
I det här exemplet ges John två husdjur, Fluffy och Furball. Funktionen Person::pet_with_name()
kallas sedan för att hämta Johns pet Whiskers. Eftersom John inte har ett husdjur som heter Whiskers misslyckas funktionen och std::nullopt
returneras istället.
#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;
}
}
valfritt som returvärde
std::optional<float> divide(float a, float b) {
if (b!=0.f) return a/b;
return {};
}
Här returnerar vi antingen fraktionen a/b
, men om den inte är definierad (skulle vara oändlig) returnerar vi istället det tomma valfritt.
Ett mer komplicerat 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 )
söker i behållaren eller intervallet some_range
efter något som är lika med numret 7
. find_if
gör det med ett predikat.
Det returnerar antingen ett tomt valfritt om det inte hittades, eller ett valfritt som innehåller ett iterator till elementet om det var.
Detta gör att du kan göra:
if (find( vec, 7 )) {
// code
}
eller ens
if (auto oit = find( vec, 7 )) {
vec.erase(*oit);
}
utan att behöva röra med start / slut iteratorer och tester.
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
returnerar antingen värdet lagrat i det valfria, eller argumentet om det inte finns något lagring där.
Detta gör att du kan ta tillfället kanske null och ge ett standardbeteende när du faktiskt behöver ett värde. Genom att göra det på detta sätt kan "standardbeteende" -beslutet skjutas tillbaka till den punkt där det bäst görs och omedelbart behövs, istället för att generera något standardvärde djupt i tarmarna i någon motor.