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. Le site ne contient pas encore d'exemples, ce qui me laisse plus de mal à saisir le vrai concept de cet objet. Quand est-il 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).
c++
boost-optional
c++-tr2
0x499602D2
la source
la source
optional
. Imaginez que vous vouliez unoptional<int>
ou même<char>
. Pensez-vous vraiment que c'est "Zen" d'être obligé d'allouer, de déréférencer et de supprimer dynamiquement - quelque chose qui autrement ne nécessiterait aucune allocation et s'insérerait de manière compacte sur la pile, et peut-être même dans un registre?std::optional
(alors qu'il peut l'être pourstd::unique_ptr
). Plus précisément, la norme exige que T [...] satisfasse aux exigences de Destructible .Réponses:
L'exemple le plus simple auquel je puisse penser:
La même chose peut être accomplie avec un argument de référence à la place (comme dans la signature suivante), mais l'utilisation
std::optional
rend la signature et l'utilisation plus agréables.Une autre façon de procéder est particulièrement mauvaise :
Cela nécessite une allocation de mémoire dynamique, se soucier de la propriété, etc. - préférez toujours l'une des deux autres signatures ci-dessus.
Un autre exemple:
C'est extrêmement préférable à la place 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é des données, ce qui est idéal pour les performances.Un autre exemple:
Si la recherche ne contient pas une certaine clé, nous pouvons simplement renvoyer «aucune valeur».
Je peux l'utiliser comme ceci:
Un autre exemple:
Cela a beaucoup plus de sens que, disons, avoir quatre surcharges de fonctions qui prennent toutes les combinaisons possibles de
max_count
(ou non) etmin_match_score
(ou pas)!Il élimine également le maudit "Passer
-1
pourmax_count
si vous ne voulez pas de limite" ou "Passerstd::numeric_limits<double>::min()
pourmin_match_score
si vous ne voulez pas un score minimum"!Un autre exemple:
Si la chaîne de requête n'est pas incluse
s
, je veux "nonint
" - pas la valeur spéciale que quelqu'un a décidé d'utiliser à cette fin (-1?).Pour des exemples supplémentaires, vous pouvez consulter la
boost::optional
documentation .boost::optional
etstd::optional
sera fondamentalement identique en termes de comportement et d'utilisation.la source
std::optional<T>
est juste unT
et unbool
. Les implémentations des fonctions membres sont extrêmement simples. Les performances ne devraient pas vraiment être un problème lors de son utilisation - il y a des moments où quelque chose est facultatif, auquel cas c'est souvent l'outil approprié pour le travail.std::optional<T>
est beaucoup plus complexe que cela. Il utilise le placementnew
et bien d'autres choses avec un alignement et une taille appropriés afin d'en faire un type littéral (c'est-à-dire utiliser avecconstexpr
) entre autres. La naïveT
et l'bool
approche échoueraient assez rapidement.union storage_t { unsigned char dummy_; T value_; ... }
Line 289:struct optional_base { bool init_; storage_t<T> storage_; ... }
Comment n'est-ce pas "aT
et abool
"? Je suis tout à fait d'accord que la mise en œuvre est très délicate et non triviale, mais conceptuellement et concrètement, le type est aT
et abool
. "La naïveT
et l'bool
approche échoueraient assez rapidement." Comment pouvez-vous faire cette déclaration en regardant le code?struct{bool,maybe_t<T>}
le syndicat est juste là pour ne pas fairestruct{bool,T}
ce qui construirait un T dans tous les cas.std::unique_ptr<T>
etstd::optional<T>
remplissent en un certain sens le rôle d '«facultatifT
». Je décrirais la différence entre eux comme des "détails d'implémentation": allocations supplémentaires, gestion de la mémoire, localité des données, coût du déplacement, etc. Je ne l'aurais jamais faitstd::unique_ptr<int> try_parse_int(std::string s);
par exemple, car cela provoquerait une allocation pour chaque appel même s'il n'y a aucune raison de . Je n'aurais jamais une classe avec unstd::unique_ptr<double> limit;
- pourquoi faire une allocation et perdre une localité de données?Un exemple est cité dans le nouveau document adopté: N3672, std :: facultatif :
la source
int
ou non une hiérarchie d'appel ascendante ou descendante, au lieu de transmettre une valeur «fantôme» «supposée» avoir une signification «erreur».str2int()
implémenter la conversion comme il le souhaite, (B) quelle que soit la méthode d'obtention dustring s
, et (C) transmet tout son sens via la méthode auoptional<int>
lieu d'une stupidebool
méthode basée sur les nombres magiques, les références ou l'allocation dynamique. je le fais.Tenez compte du fait que 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 des données à partir d'un socket, et lorsqu'un bloc de données est complet, vous l'analysez et le renvoyez:
Si les données ajoutées ont complété un bloc analysable, vous pouvez le traiter; sinon, continuez à lire et à ajouter des données:
Edit: concernant le reste de vos questions:
Lorsque vous calculez une valeur et que vous devez la renvoyer, la sémantique est meilleure pour renvoyer par valeur que pour 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 doit vérifier la valeur de sortie (celui qui écrit le code client peut ne pas rechercher d'erreur - si vous essayez d'utiliser un pointeur non initialisé, vous obtenez un vidage de mémoire; si vous essayez d'utiliser un initialized std :: optional, vous obtenez une exception catchable).
Avant C ++ 11, vous deviez utiliser une interface différente pour les «fonctions qui peuvent ne pas renvoyer une valeur» - soit retourner par pointeur et vérifier 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 client pour bien faire les choses et les deux sont source de confusion (le premier poussant l'implémenteur client à penser à une opération comme une allocation et exigeant que le code client implémente une logique de gestion de pointeur et le second permettant code client pour éviter d'utiliser des valeurs invalides / non initialisées).
std::optional
s'occupe bien des problèmes liés aux solutions précédentes.la source
boost::
placestd::
?boost::optional
car après environ deux ans d'utilisation, il est codé en dur dans mon cortex pré-frontal).J'utilise souvent des options pour représenter des données optionnelles extraites de fichiers de configuration, c'est-à-dire où ces données (comme avec un élément attendu, mais pas nécessaire, dans un document XML) sont éventuellement fournies, de sorte que je puisse montrer explicitement et clairement si les données étaient effectivement présentes dans le document XML. Surtout quand les données peuvent avoir un état "non défini", par rapport à un état "vide" et un état "défini" (logique floue). Avec un optionnel, set and not set est clair, également vide serait clair avec la valeur 0 ou null.
Cela peut montrer comment la valeur de "non défini" n'est pas équivalente à "vide". Dans le concept, un pointeur vers un int (int * p) peut le montrer, où un nul (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, j'ai un morceau de géométrie extrait d'un document XML qui avait une valeur appelée indicateurs de rendu, où la géométrie peut soit remplacer les indicateurs de rendu (ensemble), désactiver les indicateurs de rendu (définis sur 0), ou tout simplement pas affectent les indicateurs de rendu (non définis), une option serait un moyen clair de représenter cela.
Clairement, un pointeur vers 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 nul signifie littéralement non alloué ou créé, bien que cela puisse , mais ne signifie pas nécessairement «non défini». Il convient de souligner qu'un pointeur doit être libéré, et dans les bonnes pratiques mis à 0, cependant, comme avec un pointeur partagé, un optionnel ne nécessite pas de nettoyage explicite, donc il n'y a pas de souci de mélanger le nettoyage avec l'option n'ayant pas été définie.
Je pense que c'est 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 extrêmement précieuse.
L'utilisation d'un pointeur pour représenter cela nécessiterait de surcharger le concept du pointeur. Pour représenter «null» comme «non défini», 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 facultative, cependant, j'opte toujours pour une implémentation implicite plutôt que des commentaires explicites, car les commentaires ne sont pas exécutoires (comme par compilation). Des exemples de ces éléments implicites pour le développement (les articles en développement qui sont fournis uniquement pour appliquer l'intention) incluent 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 dire que vous n'avez pas vraiment besoin de ces fonctionnalités de code, tant que tout le monde obéit aux intentions ou aux commentaires.
la source