Mappage dynamic de la valeur enum (int) à taper

Il est apparu que ce problème est assez courant dans notre travail.

Nous envoyons une valeur int ou enum via le réseau, puis nous la recevons et souhaitons créer / appeler un object / une fonction en particulier.

La solution la plus simple serait d’utiliser l’instruction switch, comme ci-dessous:

switch (value) { case FANCY_TYPE_VALUE: return new FancyType(); } 

Cela fonctionne bien, mais nous aurions beaucoup de ces blocs de commutateurs, et lorsque nous créerions de la valeur et du type, nous aurions besoin de les changer tous. Cela semble juste.

Une autre possibilité serait d’utiliser les modèles. Mais nous ne pouvons pas, car la valeur de enum est définie dans le runtime.

Existe-t-il un bon modèle de conception ou une bonne approche?

Cela semble être un problème très général et courant dans le codage quotidien …

Vous pouvez réellement le faire avec quelques astuces de modèles:

 #include  template  class EnumFactory { public: static Base* create(Enum e) { typename std::map*>::const_iterator const it = lookup().find(e); if (it == lookup().end()) return 0; return it->second->create(); } protected: static std::map*>& lookup() { static std::map*> l; return l; } private: virtual Base* create() = 0; }; template  class EnumFactoryImpl : public EnumFactory { public: EnumFactoryImpl(Enum key) : position(this->lookup().insert(std::make_pair*>(key,this)).first) { } ~EnumFactoryImpl() { this->lookup().erase(position); } private: virtual Base* create() { return new Der(); } typename std::map*>::iterator position; }; 

Cela vous permet de créer un nouvel object dérivé à partir d’une enum donnée, en disant

 // will create a new `FancyType` object if `value` evaluates to `FANCY_TYPE_VALUE` at runtime EnumFactory::create(value) 

Cependant, vous devez avoir des objects EnumFactoryImpl, qui peuvent être statiques dans certaines fonctions ou dans un espace de noms.

 namespace { EnumFactoryImpl const fi1(ENUM_VALUE_1); EnumFactoryImpl const fi2(ENUM_VALUE_2); EnumFactoryImpl const fi3(ENUM_VALUE_3); EnumFactoryImpl const fi1(FANCY_TYPE_VALUE); // your example } 

Ces lignes constituent le seul point où votre code source mappe les valeurs enum en types dérivés. Vous avez donc tout au même endroit et aucune redondance (cela élimine le problème de l’oubli de le changer à certains endroits, lors de l’ajout de nouveaux types dérivés).

Essayez une carte:

 struct Base { }; struct Der1 : Base { static Base * create() { return new Der1; } }; struct Der2 : Base { static Base * create() { return new Der2; } }; struct Der3 : Base { static Base * create() { return new Der3; } }; std::map creators; creators[12] = &Der1::create; creators[29] = &Der2::create; creators[85] = &Der3::create; Base * p = creators[get_id_from_network()](); 

(Ceci est bien sûr très grossier; à tout le moins, vous auriez une vérification d’erreur et un système d’auto-inscription par classe afin que vous ne puissiez pas oublier d’inscrire une classe.)

Une option consiste à maintenir un dictionnaire de créateurs (qui a la même interface) pouvant créer un type concret. Désormais, le code de création va rechercher dans le dictionnaire une valeur int (résultant de l’énumération envoyée par le client) et appeler la méthode create, qui renvoie l’object concret via un pointeur de classe de base.

Le dictionnaire peut être initialisé à un endroit avec les créateurs concrets correspondant à chaque valeur d’énumération possible.

Le problème ici est que vous devez étendre ce code d’initialisation du dictionnaire lorsque vous ajoutez un nouveau type d’object. Une façon d’éviter est comme suit.

  1. Laissez le créateur rechercher une instance de fabrique singleton et s’inscrire dans le constructeur avec le type enums (entiers) avec lequel il peut créer un object concret.
  2. Créez une DLL pour un / groupe de créateurs et disposez d’une instance globale des créateurs.
  3. Le nom de la DLL peut être entré dans un fichier de configuration qui est lu par la fabrique lors de l’initialisation. La fabrique charge toutes les DLL de ce fichier, ce qui entraîne la création d’objects statiques qui s’enregistrent eux-mêmes auprès de la fabrique.
  4. Maintenant, l’usine a la carte de tous les types d’énums qu’elle peut créer avec les créateurs d’objects concrets.
  5. Le même mécanisme de recherche de créateur d’object est implémenté pour créer les objects.

Maintenant, l’usine n’a pas du tout besoin d’être étendue puisque les étapes 3, 4 et 5 ne changent pas pour les nouveaux objects introduits. L’étape 1 peut être mise en œuvre à un endroit.

La seule chose à faire est d’append un object global à chaque nouveau type concret, car le C ++ ne prend pas en charge la reflection de manière native.

Kogut, je ne propose pas cela comme réponse, mais puisque vous me demandez de développer mon commentaire sur votre question initiale, voici un très bref résumé de ce que l’environnement .net vous offre …

 public enum MyEnum { [MyAtsortingbute(typeof(ClassNone))] None, [MyAtsortingbute(typeof(ClassOne))] One, [MyAtsortingbute(typeof(ClassTwo))] Two, [MyAtsortingbute(typeof(ClassThree))] Three } 

Donc, vous avez votre enum de base Un, Deux, Trois etc. qui fonctionne exactement comme… euh… un enum!

Mais vous codez également une classe appelée MyAtsortingbute (et en fait, pour plus d’informations dans cette zone, recherchez simplement Atsortingbutes). Mais comme vous pouvez le constater, cela vous permet de dire, au moment de la conception, que telle ou telle valeur enum est associée à telle ou telle classe.

Ces informations sont stockées dans les métadonnées de l’énum (la valeur d’un environnement géré!) Et peuvent être interrogées à l’exécution (à l’aide de Reflection). Inutile de dire que c’est très puissant, j’ai utilisé ce mécanisme pour supprimer systématiquement de nombreuses cartes du type de celles proposées dans d’autres réponses à votre question.

Voici un exemple d’utilité: chez un client avec lequel j’ai travaillé, la convention était de stocker les statuts sous forme de chaînes dans une firebase database, au motif qu’ils seraient plus lisibles par un humain qui aurait besoin d’exécuter une requête sur table. Mais cela n’avait aucun sens dans les applications, où les statuts étaient définis comme des énumérations. Adoptez l’approche ci-dessus (avec une chaîne plutôt qu’un type) et cette transformation s’est produite sur une seule ligne de code lors de la lecture et de l’écriture des données. De plus, bien sûr, une fois que vous avez défini MyAtsortingbute, il peut être étiqueté sur l’énumération de votre choix.

Ma langue si le choix est c # de nos jours, mais cela serait également utile en c ++ (géré)