web-dev-qa-db-fra.com

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 quand je devrais l’utiliser ou comment je devrais l’utiliser il. Le site ne contient pas encore d'exemples, ce qui me laisse plus difficile de saisir le vrai concept de cet objet. Quand est std::optional un bon choix à utiliser, et comment compense-t-il ce qui n’était pas trouvé dans la norme précédente (C++ 11).

116
0x499602D2

L'exemple le plus simple auquel je puisse penser:

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 pourrait être accomplie avec un argument de référence (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 manière de procéder est particulièrement mauvaise :

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

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


Un 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 plutôt que d'avoir quelque chose comme un std::unique_ptr<std::string> Pour chaque numéro de téléphone! std::optional Vous donne la localité de données, ce qui est excellent pour la performance.


Un 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 ça:

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

Un 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, disons, d'avoir quatre surcharges de fonctions prenant toutes les combinaisons possibles de max_count (Ou pas) et de min_match_score (Ou non)!

Il élimine également le maudit "Passez -1 Pour max_count Si vous ne voulez pas de limite "ou" Passez std::numeric_limits<double>::min() pour min_match_score Si vous ne voulez pas de score minimum "!


Un 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" - et non quelle que soit la valeur spéciale quelqu'un a décidé d'utiliser à cette fin (-1?).


Pour des exemples supplémentaires, vous pouvez regarder le boost::optionaldocumentation . boost::optional Et std::optional Seront fondamentalement identiques en termes de comportement et d'utilisation.

164
Timothy Shields

Un exemple est cité dans Nouvel article 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
         }
     }
}
31
taocp

mais je ne comprends pas quand je devrais l'utiliser ou comment je devrais l'utiliser.

Pensez lorsque vous écrivez une API et que vous souhaitez exprimer que la valeur "ne pas avoir de retour" n'est pas une erreur. Par exemple, vous devez lire les données d'un socket et, lorsqu'un bloc de données est terminé, vous l'analysez et vous 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 ont complété un bloc analysable, vous pouvez le 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, cela donne une meilleure sémantique à renvoyer par valeur que de prendre une référence à une valeur en sortie (qui peut ne pas être générée).

  • Lorsque vous voulez vous assurer que le code client a vérifie la valeur de sortie (quiconque écrit le code client peut ne pas rechercher d'erreur) - si vous essayez d'utiliser un pointeur non initialisé, vous obtenez un vidage mémoire; vous essayez d'utiliser un std :: optionnel non initialisé, vous obtenez une exception interceptable).

[...] et comment compense-t-il ce qui n'avait 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 retourner par pointeur et rechercher la valeur NULL, ou accepter un paramètre de sortie et renvoyer un code d'erreur/résultat pour "indisponible". ".

Les deux imposent un effort et une attention supplémentaires de la part de l'implémenteur client pour bien faire les choses et sont une source de confusion (le premier poussant l'implémenteur client à penser à une opération comme une allocation et exigeant du code client l'implémentation d'une logique de traitement du pointeur et le second permettant code client à utiliser avec des valeurs non valides/non initialisées).

std::optional résout bien les problèmes posés par les solutions précédentes.

9
utnapistim

J'utilise souvent des options pour représenter des données facultatives extraites de fichiers de configuration, c'est-à-dire où ces données (comme avec un élément attendu, mais non nécessaire, dans un document XML) sont éventuellement fournies, afin de pouvoir indiquer clairement 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 "ensemble" (logique floue). Avec une option, définir et non définir est clair, également vide serait clair avec la valeur 0 ou null.

Cela peut montrer que la valeur de "non définie" n'est pas équivalente à "vide". En principe, un pointeur sur un int (int * p) peut indiquer cela, lorsqu'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éfini sur une valeur.

Pour un exemple pratique, une partie de la géométrie extraite d'un document XML ayant une valeur appelée drapeaux de rendu, la géométrie peut remplacer les drapeaux de rendu (définir), désactiver les drapeaux de rendu (définir à 0) ou tout simplement ne pas affecter les indicateurs de rendu (non définis), une option serait un moyen clair de le représenter.

Clairement, 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 clarté du code dans ce cas. Un null est-il toujours un "non défini"? Avec un pointeur, ce n'est pas clair, car null signifie littéralement non alloué ou créé, bien que pourrait, mais pourrait pas nécessairement signifie "non défini". Il convient de souligner qu’un pointeur doit être libéré et que, dans la pratique, il doit être défini sur 0. Toutefois, comme avec un pointeur partagé, une option ne nécessite pas de nettoyage explicite, il n’est donc pas inutile de mélanger le nettoyage avec l'option n'est pas définie.

Je crois que c'est à propos de la clarté du code. La clarté réduit les coûts de maintenance et de développement du code. Une compréhension claire de l'intention du code est extrêmement utile.

L'utilisation d'un pointeur pour représenter cela nécessiterait de surcharger le concept du pointeur. Pour représenter "null" en tant que "non défini", vous pouvez généralement voir un ou plusieurs commentaires via 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 des commentaires explicites, car les commentaires ne sont pas applicables (comme par compilation). Des exemples de ces éléments implicites pour le développement (les articles en développement fournis uniquement pour appliquer l'intention) incluent les divers transtiffs de style C++, "const" (en particulier pour les fonctions membres) et le type "bool", pour n'en nommer que quelques-uns. On peut dire que vous n'avez pas vraiment besoin de ces fonctionnalités de code, tant que tout le monde obéit à des intentions ou à des commentaires.

4
Kit10