Ricerca…


introduzione

Optionals (noto anche come Maybe types) sono usati per rappresentare un tipo il cui contenuto può o non può essere presente. Sono implementate in C ++ 17 come std::optional class std::optional . Ad esempio, un oggetto di tipo std::optional<int> può contenere un valore di tipo int , oppure potrebbe non contenere alcun valore.

Gli optionals sono comunemente usati per rappresentare un valore che potrebbe non esistere o come un tipo di ritorno da una funzione che può fallire nel restituire un risultato significativo.

Altri approcci a facoltativo

Ci sono molti altri approcci per risolvere il problema che std::optional risolve std::optional , ma nessuno di questi è abbastanza completo: usando un puntatore, usando una sentinella, o usando una pair<bool, T> .

Opzionale vs puntatore

In alcuni casi, possiamo fornire un puntatore a un oggetto esistente o nullptr per indicare un errore. Ma questo è limitato a quei casi in cui gli oggetti esistono già - optional , come un tipo di valore, può anche essere usato per restituire nuovi oggetti senza ricorrere all'assegnazione della memoria.

Opzionale vs Sentinel

Un idioma comune è usare un valore speciale per indicare che il valore non ha significato. Questo può essere 0 o -1 per i tipi interi o nullptr per i puntatori. Tuttavia, questo riduce lo spazio dei valori validi (non è possibile distinguere tra uno 0 valido e uno 0 senza significato) e molti tipi non hanno una scelta naturale per il valore sentinella.

Opzionale vs std::pair<bool, T>

Un altro idioma comune è quello di fornire una coppia, in cui uno degli elementi è un bool indica se il valore è significativo o meno.

Ciò si basa sul fatto che il tipo di valore è predefinito-constructible in caso di errore, che non è possibile per alcuni tipi e possibili ma indesiderabile per gli altri. Un optional<T> , in caso di errore, non ha bisogno di costruire nulla.

Utilizzo degli optionals per rappresentare l'assenza di un valore

Prima di C ++ 17, avere puntatori con un valore di nullptr rappresentava comunemente l'assenza di un valore. Questa è una buona soluzione per oggetti di grandi dimensioni che sono stati allocati dinamicamente e sono già gestiti da puntatori. Tuttavia, questa soluzione non funziona bene per i tipi piccoli o primitivi come int , che raramente vengono allocati o gestiti dinamicamente dai puntatori. std::optional fornisce una soluzione praticabile a questo problema comune.

In questo esempio, struct Person è definito. È possibile che una persona abbia un animale domestico, ma non è necessario. Pertanto, il membro pet di Person è dichiarato con un wrapper std::optional .

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

Utilizzo degli optionals per rappresentare l'errore di una funzione

Prima del C ++ 17, una funzione in genere rappresentava un errore in uno dei seguenti modi:

  • È stato restituito un puntatore nullo.
    • Ad es. Chiamare una funzione Delegate *App::get_delegate() su un'istanza di App che non aveva un delegato restituirebbe nullptr .
    • Questa è una buona soluzione per oggetti che sono stati allocati dinamicamente o sono grandi e gestiti da puntatori, ma non è una buona soluzione per gli oggetti di piccole dimensioni che sono in genere allocati allo stack e passati copiando.
  • Un valore specifico del tipo restituito è stato riservato per indicare un errore.
    • Ad es. Chiamando una funzione unsigned shortest_path_distance(Vertex a, Vertex b) su due vertici che non sono collegati può restituire zero per indicare questo fatto.
  • Il valore è stato accoppiato con un bool per indicare che il valore restituito era significativo.
    • es. Chiamando una funzione std::pair<int, bool> parse(const std::string &str) con un argomento stringa che non è un intero restituirebbe una coppia con un indefinito int e un bool impostato su false .

In questo esempio, a John vengono dati due animali domestici, Fluffy e Furball. La funzione Person::pet_with_name() viene quindi chiamata per recuperare i Whiskers di animali domestici di John. Poiché John non ha un animale domestico di nome Whiskers, la funzione fallisce e std::nullopt viene invece restituito.

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

opzionale come valore di ritorno

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

Qui restituiamo la frazione a/b , ma se non è definita (sarebbe infinita), restituiamo invece l'opzione vuota.

Un caso più complesso:

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 ) cerca nel contenitore o range some_range per qualcosa uguale al numero 7 . find_if fa con un predicato.

Restituisce un opzionale vuoto se non è stato trovato, o un opzionale contenente un iteratore per l'elemento se lo era.

Questo ti permette di fare:

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

o anche

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

senza dover scherzare con iteratori di inizio / fine e test.

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 o restituisce il valore memorizzato value_or , o l'argomento se non c'è niente memorizza lì.

Ciò ti consente di prendere l'opzionale may-zero e di dare un comportamento predefinito quando hai effettivamente bisogno di un valore. In questo modo, la decisione del "comportamento predefinito" può essere rinviata al punto in cui è meglio realizzata e immediatamente necessaria, invece di generare un valore predefinito nelle profondità di alcuni motori.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow