Utilisez-vous boost::variant
ou l'une des nombreuses implémentations Open source C ++ 11 d'une "Union taguée" ou un type de variant dans vos projets C ++?
boost::variant
est une excellente bibliothèque. J'ai créé strict_variant
afin de répondre à quelques choses sur boost::variant
que je n'aimais pas.
La version TL; DR est que, contrairement à boost::variant
ou std::variant
, strict_variant
ne lancera jamais une exception ni ne fera une allocation dynamique dans l'effort des types de support qui ont des mouvements de lancer. La version par défaut échouera simplement à une affirmation statique si cela se produisait. Le strict_variant::easy_variant
fera des allocations dans cette situation, vous pouvez donc vous en opposer si vous le souhaitez, et ces deux versions de la variante "jouent bien" ensemble. Ce genre de chose est souvent une préoccupation majeure dans les projets avec des exigences en temps réel, ou dans les appareils intégrés, qui peuvent ne pas permettre, ou tout simplement ne pas avoir ces fonctionnalités C ++. Si vous créez une bibliothèque qui pourrait être utilisée dans des projets "conventionnels" qui souhaitent la facilité d'utilisation qui vient de boost::variant
, mais peut également être utilisé dans des projets avec des exigences restrictives, et vous souhaitez utiliser un type variant
Dans le cadre de l'API, strict_variant
pourrait offrir un moyen de garder tout le monde heureux.
En plus de cela, il y a des problèmes dans l'interface de variante qui ont été abordés qui rendent plus agréable d'utiliser un IMHO au jour le jour. (C'étaient en fait la motivation originale du projet.)
Je n'ai pas aimé ce code comme celui-ci peut se compiler sans aucun avertissement ou messages d'erreur:
boost::variant<std::string, int > v;
v = true ;
Je préfère généralement que ma variant
soit plus restrictive sur les conversions implicites.
Je voulais que des choses comme celle-ci se compilent et fassent ce qui a du sens, même si la résolution de surcharge serait ambiguë.
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
Je voulais aussi qu'un tel comportement (ce qui est sélectionné dans de tels cas) soit portable.
(Pour des exemples de code comme celui-ci, Where boost::variant
a un comportement malheureux, voir "Résumé et motivation" dans la documentation.)
Dans strict_variant
nous modifions la résolution de surcharge dans ces situations en supprimant certains candidats.
Par exemple:
bool
, intégrale, point flottant, pointeur, caractère et certaines autres classes.int -> long
et int -> long long
sont des candidats, la long long
est éliminée.Voir la documentation pour plus de détails.
Je n'ai pas aimé ce boost::variant
fera silencieusement des copies de sauvegarde de mes objets. Par exemple, considérez ce programme simple, dans lequel A
et B
ont été définis pour enregistrer tous les appels CTOR et DTOR.
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
La boost::variant
produit la sortie suivante:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
Cela peut être assez surprenant pour certains programmeurs.
En revanche, si vous utilisez la std::variant
, ou l'une des variantes avec une sémantique "parfois-vide", vous obtenez quelque chose comme ça (cette sortie de std::experimental::variant
)
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
Ceci est beaucoup plus proche de ce que le programmeur naïf attend qui ne connaît pas les détails internes de boost::variant
- les seules copies de ses objets qui existent sont ce qu'il peut voir dans son code source.
Ce genre de chose n'a généralement pas d'importance, mais parfois si par exemple vous déboguez un problème de corruption de mémoire désagréable (il y a peut-être un mauvais code dans l'un des objets contenus dans la variante), alors ces objets, mouvements et copies supplémentaires peuvent Rendre les choses incidemment plus compliquées.
Voici ce que vous obtenez avec strict_variant
:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
Pourtant, strict_variant
n'a pas d'état vide et est pleinement exceptionnel!
(Ces exemples de gcc 5.4
, voir le code dans example
de dossier.)
Pour résumer les différences:
std::variant
est rarement en vigueur, toujours basée sur la pile. En fait, il est vide exactement lorsqu'une exception est lancée. Plus tard, il lance différentes exceptions si vous essayez de visiter lorsqu'il est vide.boost::variant
n'est jamais vide, généralement basée sur la pile. Il doit faire une allocation dynamique et une copie de sauvegarde chaque fois qu'une exception peut être lancée, mais cela est libéré juste après si une exception n'est pas réellement lancée.strict_variant
n'est jamais vide et basé sur la pile exactement lorsque le type de valeur actuel n'est rien mobile. Il ne fait jamais un mouvement ou une copie de sauvegarde, et ne lance jamais une exception. Chaque approche a ses mérites. J'ai choisi l'approche strict_variant
parce que je trouve cela plus simple et cela évite ce que je considère comme des inconvénients de boost::variant
et std::variant
. Et, si vous parvenez à rendre tous vos types, sans lancer, ce que je trouve souvent, strict_variant
vous donne des performances optimales, identiques à std::variant
sans état vide.
Pour une discussion approfondie de la conception, consultez la documentation.
Pour une douce introduction aux variantes et un aperçu de stricte-variant, voir les diapositives d'un discours que j'ai donné à ce sujet: [PPTX] [PDF]
Sur les pages GitHub.
strict_variant
cible la norme C ++ 11.
Il est connu pour fonctionner avec gcc >= 4.8
et clang >= 3.5
, et est testé contre MSVC 2015
.
strict_variant
peut être utilisé tel quel dans les projets qui nécessitent -fno-exceptions
et -fno-rtti
.
Une variante stricte est disponible sous la licence de logicielle Boost.