2008-12-29 5 views
7

Quelle est la meilleure façon d'avoir un tableau associatif avec les types de valeurs arbitraires pour chaque touche en C++?C++ de tableau associatif avec des types arbitraires pour les valeurs

Actuellement, mon plan est de créer une classe « valeur » avec des variables membres des types j'attendrai. Par exemple:

class Value { 

    int iValue; 
    Value(int v) { iValue = v; } 

    std::string sValue; 
    Value(std::string v) { sValue = v; } 

    SomeClass *cValue; 
    Value(SomeClass *v) { cValue = c; } 

}; 

std::map<std::string, Value> table; 

Un inconvénient avec c'est que vous devez connaître le type lors de l'accès à la « valeur ». i.e .:

table["something"] = Value(5); 
SomeClass *s = table["something"].cValue; // broken pointer 

De même, plus le nombre de types mis en Valeur est élevé, plus le tableau sera boursouflé.

De meilleures suggestions?

+0

BTW, une std :: map n'est pas une table de hachage; il est généralement implémenté comme un arbre rouge-noir, mais dans tous les cas, les clés sont conservées dans l'ordre. Il y a std :: tr1 :: unordered_map, qui est généralement implémenté en tant que table de hachage. –

+0

Chris, merci de l'avoir signalé. Je l'appelle maintenant un "tableau associatif". –

+0

related: ["Quel est le point des tableaux hétérogènes?"] (Http://stackoverflow.com/questions/4534612/what-is-the-point-of-heterogenous-arrays) –

Répondre

2

Sous-classe Value avec IntValue, StringValue et ainsi de suite.

9

Votre approche est fondamentalement dans la bonne direction. Vous devez connaître le type dans lequel vous mettez. Vous pouvez utiliser boost::any et vous serez en mesure de mettre à peu près tout dans la carte, aussi longtemps que vous savez ce que vous mettez dans:

std::map<std::string, boost::any> table; 
table["hello"] = 10; 
std::cout << boost::any_cast<int>(table["hello"]); // outputs 10 

Quelques réponses ont recommandé l'utilisation de boost::variant pour résoudre ce problème. Mais il ne vous laissera pas stocker des valeurs typées arbitraires dans la carte (comme vous le vouliez). Vous devez connaître l'ensemble des types possibles à l'avance. Étant donné que, vous pouvez le faire ci-dessus plus facilement:

typedef boost::variant<int, std::string, void*> variant_type; 
std::map<std::string, variant_type> table; 
table["hello"] = 10; 
// outputs 10. we don't have to know the type last assigned to the variant 
// but the variant keeps track of it internally. 
std::cout << table["hello"]; 

Cela fonctionne parce que boost::variantoperator<< à cette surcharge fin. Il est important de comprendre que si vous voulez enregistrer ce qui est actuellement contenue dans la variante, vous devez toujours connaître le type, comme dans le boost::any cas:

typedef boost::variant<int, std::string, void*> variant_type; 
std::map<std::string, variant_type> table; 
table["hello"] = "bar"; 
std::string value = boost::get<std::string>(table["hello"]); 

L'ordre des affectations à une variante est un moteur d'exécution propriété du flux de contrôle de votre code, mais le type utilisé de n'importe quelle variable est déterminé au moment de la compilation. Donc, si vous voulez obtenir la valeur de la variante, vous devez connaître son type. Une alternative consiste à utiliser les visites, comme indiqué dans la documentation de la variante. Cela fonctionne car la variante stocke un code qui lui indique quel type lui a été assigné en dernier. Sur cette base, il décide lors de l'exécution qui surcharge du visiteur qu'il utilise. boost::variant est assez grand et ne se conforme pas tout à fait standard, tout en boost::any est conforme à la norme, mais utilise la mémoire dynamique, même pour les petits types (donc il est variante plus lente. Peut utiliser la pile pour les petits types). Donc, vous devez échanger ce que vous utilisez.

Si vous voulez vraiment mettre des objets dans ce qui ne diffèrent que par la façon dont ils font quelque chose, le polymorphisme est une meilleure façon d'aller. Vous pouvez avoir une classe de base qui vous dérivez de:

std::map< std::string, boost::shared_ptr<Base> > table; 
table["hello"] = boost::shared_ptr<Base>(new Apple(...)); 
table["hello"]->print(); 

Ce qui serait essentiellement besoin de cette mise en page de classe:

class Base { 
public: 
    virtual ~Base() { } 
    // derived classes implement this: 
    virtual void print() = 0; 
}; 

class Apple : public Base { 
public: 
    virtual void print() { 
     // print us out. 
    } 
}; 

Le boost::shared_ptr est un pointeur intelligent que l'on appelle. Il supprimera automatiquement vos objets si vous les supprimez de votre carte et rien d'autre ne les référencera plus. En théorie, vous auriez pu travailler avec un pointeur neutre, mais en utilisant un pointeur intelligent augmentera considérablement la sécurité.Lisez le manuel shared_ptr auquel je suis lié.

+0

C'est le type de vérification de l'exécution option. Pour une option de vérification de type lors de la compilation, il existe également boost :: variant (utiliser avec le modèle de visiteur pour une solution statique vérifiée par type). –

+0

Eh bien, nous en avons parlé dans IRC je pense :) juste pour tous les autres qui le lisent: variante ne peut stocker qu'un ensemble limité de types, et si l'on veut obtenir une valeur sur une entrée, on peut utiliser la visite (et obtenir à la valeur sans connaître son type), mais on ne peut pas enregistrer la valeur n'importe où. –

+0

parce que la décision quelle version du visiteur est appelée est faite à l'exécution. le type d'une variable est cependant déterminé au moment de la compilation. donc vous ne pouvez pas faire SomeAutoDeducedType resultat = boost :: apply_visitor (my_visitor(), u); . tout op() d'un visiteur a le même type de retour. –

2

Pouvez-vous utiliser une union avec std :: map?

Boost :: variant fournit des variables sans type. Altrnativement, vous pouvez rendre privés tous vos membres de données de la valeur et fournir des accesseurs qui renvoient une erreur (ou un jet) si elle n'est pas définie.

+0

Vous pouvez utiliser une union avec std :: map, mais les unions ne peuvent contenir que des objets de type POD. – mstrobl

1

Une optimisation simple serait d'utiliser un union, puisque vous aurez toujours seulement une des valeurs comme clé.

Une solution plus complète encapsulerait certaines informations de type temps d'exécution dans une interface. Principalement "Quel type est-ce?" et "Comment comparer les valeurs pour l'égalité?" Ensuite, utilisez les implémentations de cela comme clé.

+0

Les syndicats ne peuvent pas contenir std :: string – MSalters

+0

En effet, g ++ se plaint de "error: member 'std :: string [...]' avec le constructeur non autorisé en union" (entre autres choses). Mon C++ devient rouillé: - / –

Questions connexes