Comment les variantes et celles de la bibliothèque boost fonctionnent-elles en interne? Dans un projet sur lequel je travaille, j'utilise actuellement un syndicat tagué. Je veux utiliser autre chose, car les unions en C++ ne vous permettent pas d'utiliser des objets avec des constructeurs, des destructeurs ou des opérateurs d'affectation surchargés.
J'ai demandé la taille de n'importe quelle variante et j'ai fait quelques expériences avec eux. Dans ma plate-forme, la variante prend la taille de son type le plus long possible plus 8 octets: je pense que ce ne peut être que 8 octets d'informations de type et le reste étant la valeur stockée. D'un autre côté, tout ne prend que 8 octets. Étant donné que je suis sur une plate-forme 64 bits, je suppose que n'importe lequel contient simplement un pointeur.
Comment est-ce que Any sait quel type il contient? Comment Variant réalise-t-il ce qu'il fait grâce aux modèles? Je voudrais en savoir plus sur ces cours avant de les utiliser.
Si vous lisez le boost :: toute documentation, ils fournissent la source de l'idée: http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf
C'est la dissimulation d'informations de base, une compétence C++ essentielle à posséder. Apprend le!
Étant donné que la réponse la plus votée ici est totalement incorrecte, et j'ai des doutes que les gens vont réellement regarder la source pour vérifier ce fait, voici une implémentation de base d'une interface similaire qui encapsulera tout type avec un f() fonction et permet de l'appeler:
struct f_any
{
f_any() : ptr() {}
~f_any() { delete ptr; }
bool valid() const { return ptr != 0; }
void f() { assert(ptr); ptr->f(); }
struct placeholder
{
virtual ~placeholder() {}
virtual void f() const = 0;
};
template < typename T >
struct impl : placeholder
{
impl(T const& t) : val(t) {}
void f() const { val.f(); }
T val;
};
// ptr can now point to the entire family of
// struct types generated from impl<T>
placeholder * ptr;
template < typename T >
f_any(T const& t) : ptr(new impl<T>(t)) {}
// assignment, etc...
};
boost :: any fait la même chose de base sauf que f() retourne en fait typeinfo const&
et donne accès à d'autres informations à la fonction any_cast pour fonctionner.
La principale différence entre boost::any
et boost::variant
est que any
peut stocker n'importe quel type, tandis que variant
ne peut stocker qu'un seul parmi un ensemble de types énumérés. Le type any
stocke un void*
pointeur vers l'objet, ainsi qu'un objet typeinfo
pour mémoriser le type sous-jacent et appliquer un certain degré de sécurité de type. Dans boost::variant
, il calcule l'objet de taille maximale et utilise "placement new" pour allouer l'objet dans ce tampon. Il stocke également le type ou l'index de type.
Notez que si Boost est installé, vous devriez pouvoir voir les fichiers source dans "any.hpp" et "variant.hpp". Recherchez simplement "include/boost/variant.hpp" et "include/boost/any.hpp" dans "/ usr", "/ usr/local" et "/ opt/local" jusqu'à ce que vous trouviez les en-têtes installés, et vous pouvez jeter un oeil.
Éditer
Comme cela a été souligné dans les commentaires ci-dessous, il y avait une légère inexactitude dans ma description de boost :: any. Bien qu'il puisse être implémenté à l'aide de void*
(et un rappel de destruction basé sur un modèle pour supprimer correctement le pointeur), l'implémentation réelle utilise any<T>::placeholder*
, avec any<T>::holder<T>
en tant que sous-classes de any<T>::placeholder
pour unifier le type.
boost::any
prend juste un instantané du typeinfo
pendant l'exécution du constructeur basé sur un modèle: il a un pointeur vers une classe de base non basée sur un modèle qui donne accès à typeinfo, et le constructeur a dérivé une classe spécifique au type satisfaisant cette interface. La même technique peut en fait être utilisée pour capturer d'autres capacités communes d'un ensemble de types (par exemple le streaming, les opérateurs communs, les fonctions spécifiques), bien que boost n'offre pas le contrôle de cela.
boost :: variant est conceptuellement similaire à ce que vous avez fait auparavant, mais en n'utilisant pas littéralement un union
et en adoptant une approche manuelle de la construction/destruction des objets dans son tampon (tout en gérant explicitement les problèmes d'alignement) il contourne les restrictions que C++ a concernant les types complexes dans les union
s réels.