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å en App instans som inte hade en delegat skulle returnera nullptr .
    • 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.
  • 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.
  • 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 odefinierad int och en bool som är false .

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.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow