123 votes

Comment utiliser std::optional ?

Je lis la documentation de std::experimental::optional et j'ai une bonne idée de ce qu'il fait, mais je ne comprends pas ce qu'il fait. quand Je devrais l'utiliser ou comment je devrais l'utiliser. Le site ne contient pas encore d'exemples, ce qui fait que j'ai du mal à saisir le véritable concept de cet objet. Quand est-ce que std::optional et comment compense-t-elle ce qui n'a pas été trouvé dans la norme précédente (C++11).

169voto

Timothy Shields Points 17970

L'exemple le plus simple auquel je pense :

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}

La même chose peut être accomplie avec un argument de référence à la place (comme dans la signature suivante), mais l'utilisation de std::optional rend la signature et l'utilisation plus agréables.

bool try_parse_int(std::string s, int& i);

Une autre façon de procéder est la suivante particulièrement mauvais :

int* try_parse_int(std::string s); //return nullptr if fail

Cela nécessite une allocation dynamique de la mémoire, la prise en compte de la propriété, etc. - préférez toujours l'une des deux autres signatures ci-dessus.


Autre exemple :

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

Ceci est extrêmement préférable à l'utilisation de quelque chose comme un std::unique_ptr<std::string> pour chaque numéro de téléphone ! std::optional permet de localiser les données, ce qui est excellent pour les performances.


Autre exemple :

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

Si la recherche ne contient pas une certaine clé, nous pouvons simplement renvoyer "aucune valeur".

Je peux l'utiliser comme suit :

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

Autre exemple :

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

Cela a beaucoup plus de sens que d'avoir, par exemple, quatre surcharges de fonctions qui prennent toutes les combinaisons possibles de max_count (ou non) et min_match_score (ou pas) !

Il s'agit également élimine les maudit "Passez -1 para max_count si vous ne voulez pas de limite" ou "Passez std::numeric_limits<double>::min() para min_match_score si vous ne voulez pas de score minimum" !


Autre exemple :

std::optional<int> find_in_string(std::string s, std::string query);

Si la chaîne de requête n'est pas dans s Je veux "non int " -- pas toute valeur spéciale que quelqu'un a décidé d'utiliser à cette fin (-1 ?).


Pour d'autres exemples, vous pouvez consulter le site boost::optional la documentation . boost::optional y std::optional seront fondamentalement identiques en termes de comportement et d'utilisation.

32voto

taocp Points 14822

Un exemple est cité dans Nouveau papier adopté : N3672, std::optional :

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}

10voto

utnapistim Points 12060

mais je ne comprends pas quand ni comment je dois l'utiliser.

Imaginez que vous écriviez une API et que vous souhaitiez indiquer que le fait de ne pas avoir de valeur de retour n'est pas une erreur. Par exemple, vous devez lire des données à partir d'un socket, et lorsqu'un bloc de données est complet, vous l'analysez et le renvoyez :

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

Si les données ajoutées complètent un bloc analysable, vous pouvez les traiter ; sinon, continuez à lire et à ajouter des données :

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}

Edit : en ce qui concerne le reste de vos questions :

Quand est-ce que std::optional est un bon choix à utiliser ?

  • Lorsque vous calculez une valeur et que vous devez la renvoyer, il est préférable, d'un point de vue sémantique, de la renvoyer par sa valeur plutôt que de prendre une référence à une valeur de sortie (qui peut ne pas être générée).

  • Lorsque vous voulez vous assurer que le code client a pour vérifier la valeur de sortie (la personne qui écrit le code client peut ne pas vérifier les erreurs - si vous essayez d'utiliser un pointeur non initialisé, vous obtenez un core dump ; si vous essayez d'utiliser un std::optional non initialisé, vous obtenez une exception rattrapable).

[...] et comment elle compense ce qui n'a pas été trouvé dans la norme précédente (C++11).

Avant C++11, vous deviez utiliser une interface différente pour les "fonctions qui ne peuvent pas renvoyer de valeur" - soit renvoyer par un pointeur et vérifier la présence de NULL, soit accepter un paramètre de sortie et renvoyer un code d'erreur/résultat pour "non disponible".

Les deux imposent un effort et une attention supplémentaires de la part de l'implémenteur du client pour que tout se passe bien et les deux sont une source de confusion (la première pousse l'implémenteur du client à considérer une opération comme une allocation et exige du code client qu'il mette en œuvre une logique de gestion des pointeurs et la seconde permet au code client de s'en tirer en utilisant des valeurs non valides/non initialisées).

std::optional prend gentiment en charge les problèmes posés par les solutions précédentes.

6voto

Copperpot Points 197

J'utilise souvent les optionnels pour représenter les données optionnelles extraites des fichiers de configuration, c'est-à-dire lorsque ces données (comme un élément attendu, mais non nécessaire, dans un document XML) sont fournies de manière facultative, de sorte que je puisse montrer explicitement et clairement si les données étaient effectivement présentes dans le document XML. En particulier lorsque les données peuvent avoir un état "non défini", par opposition à un état "vide" et à un état "défini" (logique floue). Avec une option, les états "set" et "not set" sont clairs, de même que l'état "empty" serait clair avec la valeur 0 ou "null".

Cela permet de montrer que la valeur "non défini" n'est pas équivalente à "vide". En concept, un pointeur sur un int (int * p) peut montrer cela, où un null (p == 0) n'est pas défini, une valeur de 0 (*p == 0) est définie et vide, et toute autre valeur (*p <> 0) est définie à une valeur.

Pour un exemple pratique, j'ai un morceau de géométrie tiré d'un document XML qui a une valeur appelée drapeaux de rendu, où la géométrie peut soit remplacer les drapeaux de rendu (défini), désactiver les drapeaux de rendu (défini à 0), ou simplement ne pas affecter les drapeaux de rendu (non défini), une option serait un moyen clair de représenter cela.

Il est clair qu'un pointeur sur un int, dans cet exemple, peut atteindre l'objectif, ou mieux, un pointeur de partage car il peut offrir une implémentation plus propre, cependant, je dirais qu'il s'agit de la clarté du code dans ce cas. Un null est-il toujours un "not set" ? Avec un pointeur, ce n'est pas clair, car null signifie littéralement "non alloué" ou "non créé". pourrait mais pourrait pas nécessairement signifie "non défini". Il convient de souligner qu'un pointeur doit être libéré et, en bonne pratique, mis à 0. Toutefois, à l'instar d'un pointeur partagé, une option ne nécessite pas de nettoyage explicite, de sorte qu'il n'y a pas de risque de confondre le nettoyage avec l'option qui n'a pas été mise en place.

Je pense qu'il s'agit d'une question de clarté du code. La clarté réduit le coût de la maintenance et du développement du code. Une compréhension claire de l'intention du code est incroyablement précieuse.

L'utilisation d'un pointeur pour représenter cela nécessiterait de surcharger le concept de pointeur. Pour représenter "null" comme "not set", vous pouvez généralement voir un ou plusieurs commentaires dans le code pour expliquer cette intention. Ce n'est pas une mauvaise solution au lieu d'une option, cependant, j'opte toujours pour une implémentation implicite plutôt que pour des commentaires explicites, car les commentaires ne sont pas applicables (comme par la compilation). Parmi les exemples de ces éléments implicites pour le développement (ces articles dans le développement qui sont fournis uniquement pour renforcer l'intention), il y a les divers casts de style C++, "const" (en particulier sur les fonctions membres), et le type "bool", pour n'en nommer que quelques-uns. On peut soutenir qu'il n'est pas vraiment nécessaire d'utiliser ces caractéristiques du code, tant que tout le monde respecte les intentions ou les commentaires.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X