Implémentation d’une classe “variante”

Note: Je connais boost::variant , mais je suis curieux de connaître les principes de conception. Cette question principalement pour l’auto-éducation.

Original post

À mon poste actuel, j’ai trouvé une ancienne implémentation de classe de variantes. Il est implémenté avec une union et ne peut prendre en charge que quelques types de données. J’ai réfléchi à la manière de concevoir une version améliorée. Après quelques retouches, je me suis retrouvé avec quelque chose qui semble fonctionner. Cependant, j’aimerais connaître votre opinion à ce sujet. C’est ici:

 #include  #include  #include  #include  #include  #include  class Variant { public: Variant() { } template Variant(T inValue) : mImpl(new VariantImpl(inValue)), mClassName(typeid(T).name()) { } template T getValue() const { if (typeid(T).name() != mClassName) { throw std::logic_error("Non-matching types!"); } return dynamic_cast<VariantImpl*>(mImpl.get())->getValue(); } template void setValue(T inValue) { mImpl.reset(new VariantImpl(inValue)); mClassName = typeid(T).name(); } private: struct AbstractVariantImpl { virtual ~AbstractVariantImpl() {} }; template struct VariantImpl : public AbstractVariantImpl { VariantImpl(T inValue) : mValue(inValue) { } ~VariantImpl() {} T getValue() const { return mValue; } T mValue; }; boost::shared_ptr mImpl; std::ssortingng mClassName; }; int main() { // Store int Variant v(10); int a = 0; a = v.getValue(); std::cout << "a = " << a << std::endl; // Store float v.setValue(12.34); float d = v.getValue(); std::cout << "d = " << d << std::endl; // Store map typedef std::map Mapping; Mapping m; m["one"] = "uno"; m["two"] = "due"; m["three"] = "tre"; v.setValue(m); Mapping m2 = v.getValue(); std::cout << "m2[\"one\"] = " << m2["one"] << std::endl; return 0; } 

La sortie est correcte:

 a = 10 d = 12.34 m2["one"] = uno 

Mes questions SO sont:

  • Cette implémentation est-elle correcte?
  • La dissortingbution dynamic dans getValue() fonctionnera-t-elle comme prévu (je ne suis pas certain)
  • Devrais-je retourner T comme une référence const à la place? Ou puis-je compter sur l’optimisation de la valeur de retour pour intervenir?
  • Autres problèmes ou suggestions?

Mettre à jour

Merci à @templatetypedef pour ses suggestions. Cette version mise à jour utilise uniquement dynamic_cast pour vérifier si les types correspondent. Les disparités de type causées par des différences de constance sont maintenant évitées grâce aux classes TypeWrapper (que j’ai volées sans vergogne au projet Poco C ++).

Donc, ceci est la version actuelle. Cela risque toutefois de contenir quelques erreurs, car je ne connais pas bien l’idée de modifier const / ref sur les modèles de modèles. J’aurai un nouveau regard demain.

 template  struct TypeWrapper { typedef T TYPE; typedef const T CONSTTYPE; typedef T& REFTYPE; typedef const T& CONSTREFTYPE; }; template  struct TypeWrapper { typedef T TYPE; typedef const T CONSTTYPE; typedef T& REFTYPE; typedef const T& CONSTREFTYPE; }; template  struct TypeWrapper { typedef T TYPE; typedef const T CONSTTYPE; typedef T& REFTYPE; typedef const T& CONSTREFTYPE; }; template  struct TypeWrapper { typedef T TYPE; typedef const T CONSTTYPE; typedef T& REFTYPE; typedef const T& CONSTREFTYPE; }; class Variant { public: Variant() { } template Variant(T inValue) : mImpl(new VariantImpl<typename TypeWrapper::TYPE>(inValue)) { } template typename TypeWrapper::REFTYPE getValue() { return dynamic_cast<VariantImpl<typename TypeWrapper::TYPE>&>(*mImpl.get()).mValue; } template typename TypeWrapper::CONSTREFTYPE getValue() const { return dynamic_cast<VariantImpl<typename TypeWrapper::TYPE>&>(*mImpl.get()).mValue; } template void setValue(typename TypeWrapper::CONSTREFTYPE inValue) { mImpl.reset(new VariantImpl<typename TypeWrapper::TYPE>(inValue)); } private: struct AbstractVariantImpl { virtual ~AbstractVariantImpl() {} }; template struct VariantImpl : public AbstractVariantImpl { VariantImpl(T inValue) : mValue(inValue) { } ~VariantImpl() {} T mValue; }; boost::shared_ptr mImpl; }; 

Cette implémentation est proche de la correction, mais il semble y avoir quelques bugs. Par exemple, ce code:

 if (typeid(T).name() != mClassName) 

n’est pas garanti de fonctionner correctement car la fonction .name() de type_info n’est pas garantie de renvoyer une valeur unique pour chaque type. Si vous voulez vérifier si les types correspondent, vous devriez probablement utiliser quelque chose comme ceci:

 if (typeid(*mImpl) == typeid(VariantImpl)) 

Ce qui vérifie plus précisément si le type correspond. Bien sûr, vous devez faire attention aux problèmes de const , car stocker un const T et stocker un T donnera différents types.

En ce qui concerne votre question sur dynamic_cast , dans le cas que vous avez décrit, vous n’avez pas besoin d’utiliser dynamic_cast car vous avez déjà vérifié que le type correspond. Au lieu de cela, vous pouvez simplement utiliser un static_cast , puisque vous avez déjà intercepté le cas où vous avez le mauvais type.

Plus important encore, ce que vous avez défini ici est une “variante non restreinte” qui peut contenir n’importe quoi, pas seulement un petit ensemble de types restreints (ce que vous trouverez normalement dans une variante). Bien que j’aime vraiment ce code, je suggérerais plutôt d’utiliser quelque chose comme Boost.Any ou Boost.Variant, qui a été largement débogué et testé. Cela dit, félicitations pour avoir trouvé le truc clé qui fait que ce travail fonctionne!

Au risque de ne pas répondre, étant donné que vous utilisez déjà Boost, je vous recommande d’essayer Boost.Variant ou Boost.Any au lieu de lancer votre propre implémentation.

Mieux vaut utiliser std::auto_ptr , car aucune sémantique de comptage de références n’est requirejse. Je retournerais normalement par référence car il est parfaitement légal de changer la valeur dans, ou par un pointeur pour autoriser NULL.

Utilisez dynamic_cast pour faire correspondre les types, pas typeid() , et vous pouvez simplement utiliser Boost. typeid() semble devoir fournir cela, mais en réalité il ne le fait pas à cause de la nature ouverte de sa spécification, alors que dynamic_cast est toujours correct de manière exacte et non ambiguë.