2

Je travaille sur l'implémentation d'une carte de pointeurs de variables membres associés à une clé de chaîne. Toutes les variables vont d'une classe de base "BaseA" Lors de l'accès aux variables de la carte, il est seulement nécessaire d'utiliser les méthodes de classe de base (getDesc()), il n'est donc pas nécessaire de récupérer le type original.std :: map des variables de variables membres polymorphes

Ce code compile et s'exécute sous GNU g ++ 6.2.1, mais d'après ce que j'ai lu, l'utilisation de reinterpret_cast n'est pas portable et peut ne pas fonctionner avec d'autres compilateurs. Est-ce correct? Ou ce code est-il conforme aux normes C++? Existe-t-il un autre moyen de le faire sans utiliser reinterpret_cast? Une exigence est que "Vars" doit être copiable avec les implémentations de copie-contructeur et de copie-affectation par défaut.

Exemple de code:

#include <iostream> 
#include <sstream> 
#include <map> 
#include <typeinfo> 

using namespace std; 

struct BaseA 
{ 
    virtual string getDesc() = 0; 
}; 

struct A1 : BaseA 
{ 
    string getDesc() override { return "This is A1"; } 
}; 

struct A2 : BaseA 
{ 
    string getDesc() override { return "This is A2"; } 
}; 

struct Vars 
{ 
    A1 a1; 
    A2 a2; 

    map< string, BaseA Vars::* > vars; 

    Vars() 
    { 
     vars["A1_KEY"] = reinterpret_cast<BaseA Vars::*>(&Vars::a1); 
     vars["A2_KEY"] = reinterpret_cast<BaseA Vars::*>(&Vars::a2); 
    } 

    BaseA& get(const string& key) 
    { 
     auto it = vars.find(key); 
     if (it != vars.end()) 
     { 
      return this->*(it->second); 
     } 
     throw std::out_of_range("Invalid variable key:[" + key + "]"); 
    } 
};         

int main() 
{ 
    Vars v; 

    cout << "a1 description :" << v.get("A1_KEY").getDesc() << endl; 
    cout << "a2 description :" << v.get("A2_KEY").getDesc() << endl; 

    return 0; 
} 
+1

Je ne recommande pas d'utiliser 'std :: string' comme clé pour' std :: map'. Si possible (comme dans la plupart des cas), utilisez un 'enum'. – DeiDei

+0

Merci DeiDei. J'utilise enum quand c'est possible, mais dans ce cas, l'idée est que la clé est entrée par l'utilisateur comme une ligne de commande, donc je comprends qu'une chaîne s'adapte mieux – SNJ

Répondre

2

Oui, il y a des very few guarantees sur ce reinterpret_cast va faire, et la coulée d'un pointeur vers un membre à l'autre n'est pas un d'entre eux (sauf si vous sauvegardez ensuite coulé le type d'origine, ce qui ne vous aide pas vraiment).

Le moyen sûr et facile à faire celui-ci utilise std::function:

struct Vars 
{ 
    A1 a1; 
    A2 a2; 

    map< string, std::function<BaseA&(Vars&)> > vars; 

    Vars() 
    { 
     vars["A1_KEY"] = &Vars::a1; 
     vars["A2_KEY"] = &Vars::a2; 
    } 

    BaseA& get(const string& key) 
    { 
     auto it = vars.find(key); 
     if (it != vars.end()) 
     { 
      return it->second(*this); 
     } 
     throw std::out_of_range("Invalid variable key:[" + key + "]"); 
    } 
}; 

Notez que si vous ne devez jamais le dictionnaire vars changer, vous pourriez transformer en un membre static const. (Cela signifierait que vous devez le définir et l'initialiser en dehors de la classe dans un fichier source.)

+0

Pourriez-vous expliquer un peu comment cela fonctionne ... Comment pourrait ' & Vars :: a1' être converti en une fonction? – Phil1970

+0

@ Phil1970 La norme ne dit pas exactement comment, seulement un pointeur-à-un membre est valide ['Callable'] (http://en.cppreference.com/w/cpp/concept/Callable) et que l'invocation il fait 'arg. * ptm' ou '(* arg). * ptm' selon le type de' arg'. C'est donc similaire à écrire [ptm] (Vars & v) -> BaseA & {return v. * Ptm; } '(mais peut-être plus efficace, car une bibliothèque spécifique au compilateur * peut * utiliser en toute sécurité des choses comme des astuces' reinterpret_cast'). – aschepler

+0

Merci aschepler !!! C'est ce que je cherche !! – SNJ