10 votes

C++ : émulation de RTTI

J'ai une hiérarchie de classe comme celle-ci :

class A        { }                            //
class AA  : A  { }                            //          A
class AAA : AA { }                            //        /   \
class AAB : AA { }                            //      AA     AB
class AB  : A  { }                            //     / \     / \
class ABA : AB { }                            //   AAA AAB ABA ABB
class ABB : AB { }                            //

J'aimerais émuler RTTI (sans l'utiliser, bien sûr) pour cette hiérarchie, de telle sorte que, étant donné un pointeur/une référence à A je peux trouver son type réel (de la même manière que pour le système typeid fait), sous la forme d'un nombre entier identifiant la classe.

De plus, j'aimerais que l'ensemble des entiers identifiant mes types soit contigu et aille de 0 à N-1 (de 0 à 6 dans mon exemple) :

class A        { virtual int t(){return 0;} } //
class AA  : A  { virtual int t(){return 1;} } //            A(0)
class AAA : AA { virtual int t(){return 2;} } //          /      \
class AAB : AA { virtual int t(){return 3;} } //      AA(1)       AB(4)
class AB  : A  { virtual int t(){return 4;} } //     /   \        /    \
class ABA : AB { virtual int t(){return 5;} } // AAA(2) AAB(3) ABA(5) ABB(6)
class ABB : AB { virtual int t(){return 6;} } //

(l'ordre n'a pas vraiment d'importance : A::t pourrait retourner 3 et AAB::t 0, par exemple.


Est-il possible de laisser le compilateur attribuer les index à mes classes ?

Je pense que CRTP pourrait m'aider ; quelque chose comme :

class X : A, AssignFirstAvailableIndex< X > { }

mais je ne suis pas assez bon avec les modèles. Comment pourrais-je mettre en œuvre ce AssignFirstAvailableIndex classe modèle ?

(bien sûr, le compilateur peut voir toutes les classes au moment de la compilation).

3voto

Basilevs Points 4048

Il existe une méthode standard pour mettre en œuvre ce dont vous avez besoin. Les facettes locales standard l'utilisent pour s'identifier. Examinez l'en-tête standard "locale".

class Base {
  public:
  // Being constructed contains a new unique identifier
  class Id {
    // An id factory returns a sequence of nonnegative ints
    static int allocate() {
      static int total = 0;
      return total++;
    }
    int _local;
    public:
    Id(): _local(allocate()) {}
    int get() const {return _local;}
  };
  //Child classes should make this function return an id generated by Base::Id constructed as static member.
  virtual int id() const = 0;
};

class Child1{
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child2 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

class Child3 {
  public:
  static const Base::Id _id; 
  virtual int id() { return _id.get(); }
};

Les membres statiques peuvent être initialisés dans les fichiers d'implémentation ou être modélisés pour permettre l'instanciation directement à partir des en-têtes ou être remaniés pour une initialisation paresseuse.

0voto

Karl Knechtel Points 24349

Peut-être quelque chose comme ceci ? (la syntaxe est probablement fausse partout, mais vous voyez l'idée)

class A {
  virtual int t() { return 0; }
}

template <bool AB>
class AX<AB>: A {
  virtual int t() { return AB ? 1 : 2; }
};

template <bool AB2>
template <bool AB>
class AXX<AB2>: AX<AB> {
  virtual int t() { return AX<AB>::t() * 2 + (AB2 ? 1 : 2); }
}

... Mais sérieusement, s'il vous plaît utilisez simplement RTTI.

0voto

Je ne pense pas qu'il y ait un moyen de générer les index au moment de la compilation, sauf en utilisant une fonction enum (ce que je considère comme une approche parfaitement raisonnable). Je ne suis pas sûr qu'un modèle puisse aider, parce que les modèles sont purement fonctionnels et qu'il n'y a aucun endroit où stocker un état global (c'est-à-dire l'index actuel), sauf dans le nom du type modélisé lui-même (ce qui est exactement ce que vous essayez d'éviter).

Si vous voulez vraiment des ID entiers, il est probablement plus facile de les configurer au moment de l'exécution plutôt que d'essayer trop fort.

Tout d'abord, il faut avoir un objet qui représente un type, et utiliser l'approche artisanale typique de la RTTI : chaque classe doit avoir une instance statique de cet objet, et sa fonction virtuelle get-type-info doit retourner un pointeur vers cet objet. Ainsi, chaque classe aurait un petit bout de code, comme ceci :

static TypeInfo ms_type_info;
virtual const TypeInfo *GetTypeInfo() const {
    return &ms_type_info;
}

Et vous définissez l'info type, en mettant dans le fichier <<whatever you info you want>> quelle que soit l'information que le TypeInfo pour le rendre meilleur que le RTTI du compilateur ;

TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>);

(Toutes les implémentations que j'ai vues utilisent une macro pour automatiser la création de ces deux morceaux de texte ; c'est un peu laid, mais vos options sont limitées, et c'est mieux que de les taper).

Le site TypeInfo La structure elle-même ressemblerait un peu à ceci :

struct TypeInfo {
    int type_index;
    TypeInfo *next;
    TypeInfo(<<whatever>>) {<<see below>>}
};

Si le lecteur préfère les fonctions get et set, il peut les avoir.

Le site TypeInfo devrait être statique à la classe, et non à la fonction, car l'intention est de créer une liste de tous les objets TypeInfos . Vous avez deux options ici. L'option automatique est d'avoir chaque TypeInfo L'autre consiste à avoir une grande fonction qui ajoute manuellement ceux qu'elle veut à la liste globale.

Puis, au démarrage, exécutez les TypeInfo et leur attribuer un indice. Quelque chose comme ça pourrait faire l'affaire, en supposant qu'il y ait une TypeInfo *g_first_type_info qui pointe vers la première info type de la liste :

int next_type_index=0;
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next)
    ti->type_index=next_type_index++;

Maintenant, quand vous voulez votre ID d'entier, vous pouvez le retrouver facilement :

object->GetTypeInfo()->type_index;

Vous pourriez mettre en œuvre t assez facilement maintenant :

virtual int t() const {
    return ms_type_info.type_index;
}

Divulgation complète des problèmes auxquels je peux penser :

  • Les index de type ne sont pas définis à la compilation, donc si vous construisez des tableaux, vous devrez le faire à l'exécution. Cela peut être un gaspillage de code si vos tableaux seraient autrement des constantes au moment de la compilation - cependant pour les compilateurs qui ne supportent pas la syntaxe d'initialisation des tableaux de style C99, cela peut parfois rendre le code plus lisible.

  • Si vous aimez tout faire dans les constructeurs globaux avant que main commence, vous risquez de ne pas vous en sortir, car les TypeInfo ne sont que des objets globaux ordinaires et (dans cette situation de toute façon) ne sont pas vraiment prêts à être utilisés tant que les index de type n'ont pas été attribués.

  • Les éditeurs de liens ont tendance à supprimer les objets globaux qui ne semblent pas être utilisés, de sorte que les objets d'information sur les types dans les bibliothèques statiques peuvent ne jamais s'ajouter à la liste. Vous devez donc garder à l'esprit que si l'approche de l'enregistrement automatique fonctionne bien, quand elle ne fonctionne pas, elle ne fonctionne pas, et que vous pouvez finir par devoir enregistrer les choses manuellement.

0voto

Coyote Points 1009

J'ai utilisé avec succès la méthode décrite ici : http://www.flipcode.com/archives/Run_Time_Type_Information.shtml . En fait, chaque classe qui requiert le RTTI a une fonction statique qui renvoie son type rtti. L'avantage d'utiliser une structure pour le type est que vous pouvez ajouter des fonctions à votre structure rtti.

J'ai modifié cette approche pour permettre des choses comme component->IsOfType(CollisionComponent::GetClass()) .

J'ai également étendu la structure RTTI pour fournir un nom de classe et je suis maintenant capable d'appeler component->IsOfType("CollisionComponent") Sans inclure CollisionComponent.h.

Cette approche est très pratique lorsqu'elle est combinée avec este création de classes dynamiques. Je suis capable de créer et d'identifier des classes C++ dans des scripts et d'instancier un grand nombre de composants différents qui ne sont chargés que lorsque l'OS/matériel supporte les types nécessaires. Je suis également capable de reconfigurer les types de composants chargés en fonction de la version du serveur. Par exemple, un serveur peut autoriser l'utilisation de "CollisionComponent_V2_0" tandis qu'un autre forcera l'utilisation de "CollisionComponent_Proxy_V2"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X