2010-01-06 4 views
0

disons que j'ai quelques définitions différentes de la structure (en fait, j'ai ARROUND 50 ces définitions):C++ créer une matrice contiguë de type qui ne sait pas à compiletime

struct Type1{ 
    int i; 
    float f; 
}; 

struct Type2{ 
    bool b1; 
    bool b2; 
    double d; 
}; 

ils sont tous POD, mais peut contenir des données complètement différentes.

maintenant, au moment de l'exécution, je veux décider de quel type de ceux dont j'ai besoin et ensuite créer un tableau (ou vecteur) de ce type choisi, de sorte que toutes les données sont disposées dans la mémoire.

Comment est-ce que je ferais cela?

aussi - disons que j'ai un entier (il contient des drapeaux) qui détermine le type de ces structures dont j'ai besoin. Est-il possible comment je pourrais arranger les typedefinitions de ces struct dans un hashmap ou si telle que je puisse faire que quelque chose comme:

vector<myTypeHashMap[flagsInt]> myVect; 

?

Je sais que cela va plutôt métaprogramming thor out (dont je n'ai aucune idée :)), mais peut-être qu'il ya un moyen de le faire?

grâce

grâce

+0

Il semble que vous ayez besoin d'un motif de conception d'usine. – Dmitry

+0

Maintenant que j'y pense, même si vous parvenez à créer un tableau de l'un de ces types, l'utilisation d'un tel zoo de type va être un PITA. N'y a-t-il aucun moyen de simplifier le design? – Dmitry

+8

Ayant 50 types sans rapport semble pour moi un design plutôt douteux. –

Répondre

0

vous pouvez utiliser une struct union et std :: vecteur (note: ceci est une vieille école c technique et ne vise pas à 50 objets: D). Le struct unificateur pourrait comme ceci:

struct unifier 
{ 
    int type; 
    union 
    { 
     struct A; 
     struct B; 
    }; 
}; 

Si leur est pas une grande différence dans la taille est pas trop grande, cette méthode vous permet de mélanger et types de correspondance, ou si vous utilisez un seul type ce mécanisme vous permet de réutiliser la mémoire pour différents objets.

+0

Je ne comprends pas - si je devais créer un tableau de 'unificateur' alors chaque élément aurait la taille de ma plus grande structure, n'est-ce pas? – Mat

+0

Je ne pense pas qu'il veuille que le tableau puisse contenir tous les différents types dans différents endroits. Il a également dit qu'il avait 50 types alors un syndicat serait un gâchis d'une déclaration. Cette solution signifie également que la taille de l'élément de tableau est déterminée par la plus grande structure, qui n'est pas efficace en mémoire. –

+0

Oui, c'est l'une des forces, ou des faiblesses en fonction de votre problème. –

0

Vous pouvez faire quelque chose comme ceci:



void * buffer; 
if (a) { 
    buffer = new Type1[30]; 
} else { 
    buffer = new Type2[30]; 
} 


+0

oui - mais comme je l'ai mentionné, j'ai environ 50 tpyes différents. Dois-je créer un code spécifique à chaque cas? – Mat

+0

Vous pouvez automatiser cette génération de code avec des macros (éventuellement variées, faciles) ou bien vous pouvez creuser dans une métaprogrammation de template complexe (complexe, pour un indice google pour la librairie TypeList ou Loki d'Alexandrescu et les articles associés) –

0

Vous voulez une fonction pour créer un tableau, par le type basé sur un modèle?

template <typename T> 
T* makeArray(int n) 
{ 
    return new T[n]; 
} 

... 

Type1* arrayoftype1 = makeArray<Type1>(10); 
delete[] arrayoftype1; 
+2

Quel avantage cela a-t-il? utilisant de nouvelles? –

+0

car il peut utiliser le pointeur sur un makeArray spécifique pour activer le type - comme il le dit dans la question, par exemple std :: map

+0

hmmm - comment std :: map entrerait-il dans cette Exemple? je parlais de la hashmap pour stocker type de type dynamique, haché par un nombre entier, évalué à l'exécution – Mat

0

Vous pouvez utiliser RTTI pour écrire une instruction switch pour prendre des décisions sur le type d'un objet - lors de l'exécution.

Vous pouvez également utiliser le trait de trait pour attribuer des méta-informations à vos types: traits. Une fois que vous avez créé le trait générique et/ou les traits spécialisés pour vos types, vous pouvez envelopper le conteneur std :: vector avec un autre modèle pour créer votre objet mémoire contigu. Vous devriez créer 50 spécialisations alors essayez de vous limiter à un trait générique.

Je pense que coller entièrement à mon premier conseil vous donnera un meilleur kilométrage car vous ne voulez pas instancier 50 objets génériques pour votre type.

+0

je regarde maintenant, merci, il semble être un peu lié à mon problème – Mat

+0

hmm. mais comment RTTI m'aiderait-il? il semble seulement pour m'aider à identifier le type dynamique d'un objet, mais je ne vois pas un moyen de créer un nouvel objet d'un type déterminé dynamiquement à l'exécution? aussi, dans les limites, il est indiqué que: RTTI ne peut être utilisé avec des types polymorphes. "Cela signifie que vos classes doivent avoir au moins une fonction virtuelle, soit directement soit par héritage." Mes structures sont des types POD donc elles ne doivent pas contenir de pointeur vtable – Mat

+0

C'est correct, pour RTTI il faudrait utiliser des types polymorphes. les templates pour 50 types introduiront le programme bloat, et RTTI/union créera du code bloat en câblant une instruction switch et en définissant une énumération pour les 50 types. Vous avez dit que vous vouliez prendre des décisions lors de l'exécution pour créer des objets et les agencer de manière contiguë. Aucune des solutions possibles avec juste la langue sont des balles d'argent. Si vous voulez une solution élégante que vous avez besoin de générer le C++ en utilisant un générateur de code (google "imatix gsl"). ou faire tout simplement à l'exécution (que je suppose que vous savez comment faire). –

1

Vous pourriez utiliser une sorte d'usine. Recherche google pour "modèle d'usine C++".

quelques exemples de code simple à expliquer:

enum TypeCode { FOO, BAR, ... }; 

void* makeInstance(TypeCode t) { 
    switch(t) { 
    case FOO: return new FooStruct; 
    case BAR: return new BarStruct; 
    ... 
    } 
} 

void* makeArray(TypeCode t, size_t len) { 
    switch(t) { 
    case FOO: return new FooStruct[len]; 
    case BAR: return new BarStruct[len]; 
    ... 
    } 
} 

EDIT Exemple cartographie POO-style de TypeCode à certaines fonctionnalités et description Type:

// base class .. you may add common functionality 
class TypeTraitBase { 
public: 
    virtual void* newStruct() const = 0; 
    virtual void* newArray(size_t len) const = 0; 
    virtual size_t getSize() const = 0; 
    virtual TypeCode getCode() const = 0; 
    // add whatever else you need.. e.g. 
    virtual bool isNumeric() const { return false; } 
}; 

template <TypeCode TCode, typename TStruct> 
class TypeTrait : public TypeTraitBase { 
public: 
    virtual TStruct* newStruct() const { return new TStruct; } 
    virtual TStruct* newArray(size_t len) const { return new TStruct[len]; } 
    virtual size_t getSize() const { return sizeof(TStruct); } 
    virtual TypeCode getCode() const { return TCode; } 
}; 

/* OPTIONAL... 
// you may add specializations for some types 
// - though it is required only if you want something like isNumeric(), 
// - newStruct, newArray and so on do not require specializations! 
template < INTEGER, IntegerStruct > 
class TypeTrait : public TypeTraitBase { 
public: 
    virtual TStruct* newStruct() const { return new IntegerStruct; } 
    virtual TStruct* newArray(size_t len) const { return new IntegerStruct[len]; } 
    virtual size_t getSize() const { return sizeof(IntegerStruct); } 
    virtual TypeCode getCode() const { return INTEGER; } 
    virtual bool isNumeric() const { return true; } 
}; 
*/ 

class TypeTraitMgr { 
    static std::map<TypeCode,TypeTraitBase*> traits; 
public: 
    static void reg(TypeTraitBase* tt) { traits[tt->getCode()] = tt; } 
    static void cleanup() { /* delete all TypeTraits in traits */ } 
    static TypeTraitBase* get(TypeCode code) { return traits[code]; } 
}; 

// in .cpp file: instantiate the static member: 
std::map<TypeCode,TypeTraitBase*> traits; 


// somewhere before you use it, register all known types: 
TypeTraitMgr::reg(new TypeTrait<FOO,YourFOOStruct>); 
TypeTraitMgr::reg(new TypeTrait<BAR,YourBARStruct>); 

// use it... 
void* foo = TypeTraitMgr::get(FOO)->newStruct(); 
size_t size_of_foo = TypeTraitMgr::get(FOO)->getSize(); 

// on shutdown, cleanup type traits (they were allocated on heap, delete required) 
TypeTraitMgr::cleanup(); 

Ce code n'a pas été testé, il peut contenir des bogues;)

Notez, que cette solution a une certaine surcharge d'appel de fonction virtuelle s et similaires. Mais c'est acceptable.

De même, il peut être judicieux de fusionner TypeTraits directement dans vos structures. Cela se traduira par moins de frappe, moins de code, moins de frais généraux.

+0

mais ici j'ai encore besoin de type de code spécifique pour chacun des 50 types: S – Mat

+0

Oui, vous en aurez besoin! Si vous donnez plus de détails, alors je peux ajouter un meilleur exemple (en utilisant la méta-programmation ou similaire). Vous avez mentionné un entier contenant des drapeaux qui décrit le type? Si oui, vous aurez besoin d'une sorte de mappage entre cet entier et vous structs. – Frunsi

+0

exactement - comment pourrais-je définir et accéder à une telle cartographie? c'est bien si je dois définir cette cartographie à la main – Mat

0

Vous ne serez pas en mesure de faire ce que vous voulez en utilisant des outils de compilation comme des modèles.

Ce que vous avez probablement à faire est de gérer vous-même la mémoire. Créez une classe qui contiendra un nombre (nombre d'éléments dans le tableau) et un pointeur char *. Étant donné un désir pour N structs de type T, et un tableau int size[] donnant les tailles pour les différents Ts, allouer la mémoire avec new char(N * size[T]). Rangez la taille et vous êtes prêt à partir.

Pour accéder à la mémoire, vous pouvez utiliser operator[] et renvoyer un void *, ou une structure maîtresse décrite ci-dessous. (Ce que vous renvoyez doit être spécifié au moment de la compilation, vous ne pouvez donc utiliser aucun des 50 types de structure.)

À ce stade, vous devez disposer d'une fonction qui transforme les octets bruts en champs quelconques vous aimez, et vice versa. Ceux-ci peuvent être appelés par la fonction operator[]. Vous ne pouvez rien faire avec une structure qui n'a pas de type déterminé au moment de la compilation, donc vous aurez probablement besoin d'une structure maître avec beaucoup de champs, donc elle peut gérer tous les int s, tous les bool s, tous le float s, tous les double s et ainsi de suite l'un de vos structs va avoir. Vous aurez bien sûr besoin de tableaux pour montrer quels champs sont valides.

C'est beaucoup de travail, et je dois demander si c'est vraiment nécessaire. Est-ce que tout cela va vraiment vous acheter quelque chose d'utile?

+0

mmmh en fait j'essaie de faire quelque chose comme ça - mais si j'ai un tel 'masterstruct' - comment puis-je convertir les données brutes à ce type de structure? je n'ai pas besoin de marquer les champs valides, puisque seulement ceux que j'ai stockés efficacement sont accédés (je stocke seulement ceux qui sont accédés en premier lieu, et ceux-ci seront les mêmes qui sont accédés dans un 'replay') – Mat

0

Comment sélectionnez-vous le type à utiliser lors de l'exécution? Vous n'avez pas mentionné cela et avec ma connaissance de C++ je ne vois pas un moyen facile de le faire du tout.

Quoi qu'il en soit, une fois que vous avez sélectionné votre type de choix lors de l'exécution, vous pouvez également déterminer sa taille (en utilisant sizeof) et par conséquent créer un tableau:

size_t const size = sizeof(your_type); 
char* buffer = new char[size * count]; 
your_type* = reinterpret_cast<your_type*>(buffer); 

Maintenant, ce code fonctionne, mais il est complètement inutile car il doit connaître le type your_type, et alors vous pourriez bien sûr dire new your_type[count]. Vous pouvez cependant créer un mappage de vos types à leurs tailles, en utilisant les drapeaux entiers que vous proposiez:

enum type { tfoo, tbar }; 

std::map<type, size_t> type_sizes; 

type_sizes[tfoo] = sizeof(foo); 
type_sizes[tbar] = sizeof(bar); 

// … 

char* buffer = new char[type_sizes[type_index] * count]; 

Cependant, des solutions pratiques, vous devez compter sur l'héritage pour composer une hiérarchie de type, puis peut-être une méthode d'usine comme mentionné par d'autres.

PS: Puisque vous voulez avoir ce comportement lors de l'exécution, modèle metaprogramming n'a rien fait à voir avec cela, bien au contraire: métaprogrammation est exécuté à compilation du temps. Pourquoi avez-vous besoin qu'ils soient contigus dans la mémoire?

+0

à l'exécution, un deuxième sous-système traite les données. ce sous-système est branché sur des sous-systèmes différents (à chaque fois, la configuration est différente). chacun de ces modules stocke un intFlag décrivant les champs des données auxquelles il accède. Tout le sous-système à l'initialisation est simplement récursivement tous les drapeaux de ses modules branchés pour afficher un int décrivant quels champs des données seront accessibles. par cet int je veux choisir quel type de structure à utiliser – Mat

+0

mais ce «HOW» est le problème réel.J'ai déjà réfléchi à cette idée avec le tableau de tailles différentes, donc au moins l'allocation de mémoire serait simple - mais alors je devrais encore mapper cette mémoire à une structure spécialisée (ou routine d'accès à la mémoire spécialisée) afin de lire et écrire les données – Mat

0

Pourquoi avez-vous besoin qu'ils soient contigus en mémoire?

Je suppose que vous feriez mieux de simplement créer un tableau de pointeurs, puis d'allouer dynamiquement les classes à l'exécution. Ensuite, vous pouvez définir les pointeurs dans le tableau pour pointer vers ce que vous avez créé.

  1. Créer des classes - Pourquoi utilisez-vous des structures et non des classes? Je voudrais simplement créer une superclasse pour tous les types que vous voulez, des sous-classes pour chaque type différent, un tableau (ou une liste ou une collection) de pointeurs vers les objets, et vous pouvez les créer à l'exécution. Solution de pointeur de structure - Si vous devez utiliser des structures, vous pouvez créer une structure qui a une étiquette (pour identifier le type) et une union de pointeurs vers les différentes structures. Ce type d'accès serait sûr, aucun casting requis, et l'étiquette aiderait à prévenir les erreurs de code. Ce sera plus efficace de mémoire, comme si vous créez une union des structs réels, vous devrez allouer de la mémoire pour le plus grand.

  2. Struct Solution - Si vous en avez vraiment besoin contigu en mémoire, alors créez une structure avec un identifiant de tag et une union des différentes structs que vous voulez.

Idéalement, vous auriez une superclasse pour tous les différents types, vous

0

Essayer de chair sur la réponse de drspod ci-dessus, disons que vous faites la classe d'aide suivante:

class HelperBase { 
    virtual void *MakeArray(int n) = 0; 
    virtual void *Get(void *array, int i) = 0; 
} 
template <typename T> class Helper { 
    void *MakeArray(int n) { return new T[n]; } 
    void *Get(void *array, int i) { return &(((T*)array)[i]); } 
} 

maintenant, vous avez une carte d'entiers (ou des drapeaux, ou autre) à HelperBase s:

std::map<int, HelperBase*> GlobalMap; 
GlobalMap[1] = new HelperBase<Type1>; 
GlobalMap[2] = new HelperBase<Type2>; 
// Don't forget to eventually delete these (or use smart pointers) 

maintenant à exécution vous pouvez dire

void *array = GlobalMap[someIndex].MakeArray(n); 
void *thirdElement = GlobalMap[someIndex].Get(2); 
0

À mon avis, vous êtes mieux loti en descendant une route C au lieu de l'itinéraire d'un C.

Voici comment je résoudre votre problème, sans en savoir plus sur le domaine spécifique:

void *out_buf; 

write_type(struct &a) 
{ 
    out_buf = malloc(sizeof(a)); 
    memcpy(out_buf, a, sizeof(a)); 
} 

write_type(struct &b); //so forth and so on. 

Je ne sais pas particulièrement ce que vous essayez d'accomplir, si cela peut être un peu difficile à En tout cas, 50 types signifient beaucoup de code, quoi que vous fassiez.

+0

Comment utiliser C au lieu de C++ aider le moins peu ici? Votre code pourrait être écrit beaucoup mieux en C++ (avec des templates). Vous n'avez même pas besoin de 'malloc' et' memcpy', à la place vous pouvez utiliser l'approche montrée par moi, associée au placement 'new'. –

+0

Votre approche est raisonnablement simple et lisible: je l'aime plutôt. Cependant, l'approche hiérarchisée/axée sur les usines a une LOC/complexité * significativement * plus élevée pour atteindre le même objectif, ce qui explique pourquoi je ne suis pas d'accord avec elle et préfère l'approche plus C. –

0

Selon vos exigences, vous aurez besoin d'un Factory qui crée les objets que vous voulez. Le Factory pourrait être aussi simple qu'un std::map<*struct name*, *pointer to creation function*>. Cependant, vous aurez également besoin d'un type d'objet contenant des opérations qui peuvent être effectuées sur l'objet mystère pendant l'exécution (qui est un autre sujet).pour travailler

Pour la usine, soit vous avez besoin d'avoir tous les objets dérivés d'une classe de base commune ou d'utiliser un pointeur void * pour se référer à eux. (Ressemblant programmation générique de techniques ici ...)

La plupart des modèles de conception usine renvoient des pointeurs objets mystère. Il renvoie un pointeur vers une instance d'une classe de base; mais tout ce que vous savez c'est qu'il suit l'interface définie dans la classe de base, donc c'est un objet mystère. L'oracle dit que vous aurez besoin de connaître le type d'objet que l'usine génère afin d'effectuer des actions spéciales sur les objets. La majeure partie de votre programme sera if object is *this* type, perform *special action*. Comme l'a dit Neil, c'est là qu'intervient la nouvelle conception. Changez la perspective au point de vue de l'objet. L'objet détermine tout. Ainsi, les méthodes devraient appartenir à l'objet de sorte qu'il soit auto-suffisant. Par exemple, une fois que Factory crée un objet, l'objet lira dans ses données et affichera les annotations et les résultats. Si une méthode calculate est commune à tous les objets, elle devient alors une pure méthode virtuelle de la classe de base, forçant tous les descendants à avoir une implémentation. La bonne partie est que vous pouvez itérer sur un tableau de pointeurs à la classe de base et exécuter cette méthode calculate sans avoir besoin de connaître l'implémentation réelle.

struct CommonBase 
{ 
    virtual ResultType calculate(void) = 0; 
}; 

struct Type1 : CommonBase 
{ 
    int i; 
    float f; 
    ResultType calculate(void); // performs calculation using *i* and *f* 
}; 

struct Type2{ 
    bool b1; 
    bool b2; 
    double d; 
    ResultType calculate(void); // performs calculation using *b1*, *b2* and *d* 
}; 

//... 
std::vector<CommonBase *> the_objects; 
//... 
std::vector<CommonBase *>::iterator iter; 
for (iter = the_objects.begin(); 
    iter != the_objects.end(); 
    ++iter) 
{ 
    ResultType result; 
    result = (*iter)->calculate(); 
    std::cout << "Result: " << result << "\n"; 
} 
+0

Je ne peux pas vraiment changer la mise au point car les données à enregistrer ne sont en réalité que des données (POD). ce sont les données fournies par un sous-système et traitées par un autre. Ces données doivent être enregistrées pour une «relecture» - et à chaque étape de «traitement», seul un petit sous-ensemble des champs d'un tel objet de données est accédé (et seulement ceux qui doivent être stockés pour un relais). l'idée de la rediffusion est qu'elle devrait être effectuée en temps réel. – Mat

0

Il semble que Boost.Any pourrait être potentiellement utile, au moins pour avoir une idée de la façon de répondre à vos besoins.