Ok j'ai bien compris la procédure à suivre mais j'ai une dernière question. Tu m'as dit en gros "J'espère que tu n'as pas fait ça". Mais comment faire alors pour stocker la valeur d'un type inconnu? Quel type utiliser?
Version imprimable
Ok j'ai bien compris la procédure à suivre mais j'ai une dernière question. Tu m'as dit en gros "J'espère que tu n'as pas fait ça". Mais comment faire alors pour stocker la valeur d'un type inconnu? Quel type utiliser?
Ben, tu utilises un template ;)
A l'extrême limite, la valeur de la donnée "d'origine" peut tout à fait se trouver dans la structure qui représente les informations codées en 64 bits, après tout, le codage 64 bits et la valeur d'origine ne sont jamais que des représentation différente d'une seule et même donnée ;)
Si je reprend la structure (sous forme de template) "simplifiée (je laisse tomber les rangs et ce qui fait parasite dans l'histoire ;), tu peux très bien avoir
Les trois premières lignes significatives te permettent d'avoir toutes les informations dont tu peux avoir besoin au niveau du type de donnée à convertir, dans l'ordreCode:
1
2
3
4
5
6
7
8
9
10 template <typename Type> struct Data{ /* ca, c'est juste pour pouvoir récupérer le type en cas de besoin ;) */ usign value_type = Type ; enum{accessSize = sizeof(Type)}; // la taille réellement utilisée en mémoire par Type (en bytes ;) ) value_type original; // la valeur d'origine (dans son type d'origine) unsigned char transformed[sizeof(uint64_t)];//la représentation en 64 bits /** lsb et tous les autres */ };
- Le type de la donnée
- le nombre de bytes utilisés en mémoire pour la représenter
- la valeur avant conversion et manipulation
La quatrième ligne correspond au résultat après manipulation (en 64 bits)
Une fois que tu as cela, tu n'as plus qu'à transmettre ton objet de type Data <n'importe quel type> à une fonction ou à une classe template pour que tout tourne tout seul ;)
Fais juste attention à un fait particulier: Cette structure accepterait sans aucun problème d'être spécialisée avec la structure personne que je présentais quelques interventions plus tôt...
Mais une telle structure est beaucoup trop grande pour tenir sur 64 bits:P:?
Encore merci de tes éclaircissements :ccool: Hélas je ne sais pas si c'est parce qu'on est lundi matin mais j'ai pas compris grand chose à ta structure :aie:
// Ca c'est la nouveauté :PCode:template <typename Type>
// No comment, cava :mouarf:Code:struct Data
// Ligne que j'ai vraiment pas compris :cry: D'où sort value_type et type;Code:usign value_type = type;
// Juste une question : pourquoi un enum? :oops:Code:enum{accessSize = sizeof(Type)};
// Ca sert a quoi? :calim2:Code:value_type original;
// J'ai envie de dire pareilCode:unsigned char transformed[sizeof(uint64_t)];
Désolé si je te fais répéter mais ce serait c** de par comprendre la conclusion :(
Pourquoi pas tout simplement :
Code:
1
2
3
4
5
6 template <typename Type> { Type value; unsigned int accessSize = sizeof(Type); /*Reste*/ };
Dans mes interventions précédentes je te disais d'utiliser une structure template.
Il est cependant vrai que j'utilisais d'abord la structure var je crois ;)
une coquille de ma part. Il fallait lireCitation:
// Ligne que j'ai vraiment pas compris :cry: D'où sort value_type et type;Code:usign value_type = type;
Je corriges dans mon code ;)Code:using value_type = Type;
Sinon, typedef est déprécié au profit de using en C++11.
Quand typedef s'utilise sous la forme de typedef <type d origine > <alias pour le type>; using s'utilise sous la forme de using <alias pour le type> = <type d origine>; et permet de définir un "template typedef" (un alias qui est malgré tout encore un template, et qui spécialise partiellement un autre type template).
Cette ligne permet juste d'avoir un alias de type (value_type) qui correspond au paramètre template fourni ;)
Parce que cela en fait une constante de compilation.Citation:
// Juste une question : pourquoi un enum? :oops:Code:enum{accessSize = sizeof(Type)};
L'autre solution aurait consisté à le définir comme une variable statique constante, mais je préfères l'énumération "par habitude" :D
C'est la valeur d'origine (celle que l'utilisateur manipule).Citation:
// Ca sert a quoi? :calim2:Code:value_type original;
C'est le tableau de caractères que tu remplis lors de la conversion.Citation:
// J'ai envie de dire pareilCode:unsigned char transformed[sizeof(uint64_t)];
J'utilise un tableau de taille fixe pour que l'on ne doive pas s'inquiéter de l'allocation dynamique de la mémoire.
Je définis le nombre d'éléments à sizeof(uint64_t) pour éviter de faire la moindre supposition quant au nombre réel de bytes utilisés par une uint64_t. En effet, la seule chose dont on ait l'absolue certitude, c'est que sizeof(char) vaut 1 et que sizof(n'importe quel autre type primitif) aura une valeur qui est un multiple de 1 ;)
Je n'ai aucun problème à ce sujet là :DCitation:
Désolé si je te fais répéter mais ce serait c** de par comprendre la conclusion :(
Cela revient effectivement au même.Citation:
Pourquoi pas tout simplement :
Code:
1
2
3
4
5
6 template <typename Type> { Type value; unsigned int accessSize = sizeof(Type); /*Reste*/ };
La seule différence, c'est que j'ai défini un alias pour Type qui me permet de récupérer le type de la valeur fournie par l'utilisateur, et que j'ai donc utilisé ce type quand j'en avais besoin.
L'idée de base qui sous tend l'utilisation d'un alias de type est simplement qu'il te permet éventuellement de récupérer le type réel de la valeur quand tu es dans une fonction template.
Tu pourrais en effet envisager une fonction proche de
Si tu définis un alias de type qui correspond au paramètre template, tu peux le faire sans difficulté:Code:
1
2
3
4
5 template <typename T> void foo(Data<T> /* const &*/ data){ /* sans l'alias "value_type", si je veux récupérer le type réel de T, je fais comment ? */ /*je met quoi ici??? */ brol = data.original; }
L'idée de base est qu'un type personnalisé ou une fonction ne vaut jamais que par l'utilisation qui en est faite parce qu'un type personnalisé ou une fonction qui ne serait jamais utilisé n'aurait absolument aucun intérêt.Code:
1
2
3 void foo(Data<T> /* const &*/ data){ Data<T>:value_type brol = data.original; }
Comme il est difficile de prédire tous les cas d'utilisations que l'on pourra faire d'une structure template, il est préférable de prévoir le cas où l'on aurait besoin de travailler de manière spécifique sur le type particulier du paramètre template à un moment particulier.
Cela ne mange pas de pain, mais pourra te sortir de certaines situations difficiles :D
[EDIT]Tu pourrais d'ailleurs parfaitement envisager de créer une structure qui se contenterait de définir un certain nombre d'alias de types sous la forme de
qui pourrait parfaitement être utilisé sous une forme proche deCode:
1
2
3
4
5
6
7
8 template<typename T> struct MyTraits{ using type = T; // le type lui-même, par valeur using ref_type = T &; // le type par référence non constante using cef_type = T const &; // le type par référence constante using ptr_type = T *; // le type par pointeur usign ptr_diff = sizeof(T); // l'espace mémoire qui sépare deux variables contigues de même type };
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 template <typename T> void foo(T /* const & */ t){ MyTraits<T>::type t2 = t; // "copie" de t MyTraits<T>::ref_type ref = t; // une référence sur t MyTraits<T>::cref_type cref = t; // une référence constante sur t MyTraits<T>::ptr_type ptr = &t; // un pointeur sur t } template <typename T> void bar(std::vector <T> & tab){ std::ofstream ofs("out.bin",std::ios::binary); char * charTab = reinterpret_cast<char*>(&tab[0]);//je considère le tableau comme étant un tableau de char size_t count = tab.size(); // je sais qu'il contient count éléments de type T size_t tsize = MyTraits<T>::ptr_diff; // la taille d'un élément (en nombre de bytes) for(size_t i = 0;i<count;++i) {// je veux passer tous les éléments en revue ofs.write(&charTab[i*tsize],tsize); } /* bon, il est vrai qu'il aurait sans doute été plus facile de faire * ofs.write(&charTab[0],count*size); * mais c'était pour l'exemple :D */ }; /* une structure (mode C pour la cause) */ struct Personne{ char nom[15]; char prenom[15]; }; int main(){ std::vector<Personne> tab; /* remplissage de tab */ foo<Personne>(tab[3]); bar<Personne>(tab); }
Merci encore koala01 pour ces explications :ccool:
Déjà j'ai essayé dans un petit projet vide, de définir la structure template comme tu me l'as dit mais la ligne avec le using pose problème (expected nested-name-specifier before 'value_type') :aie:
De plus, j'ai voulu utiliser les template pour ne pas devoir faire une batterie de if pour au final faire la même chose mais je pense que je vais pas pour en échapper même si j'arrive à définir cette structure :cry:
Car quand je vais l'instancier il va bien falloir que je fasse un truc du genre :
C'est fou comme j'aurai du bien réfléchir et demander des avis avant de me lancer de d'en être la car maintenant j'ai l'impression d'être f**ked :?Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 if (utilisateur a choisit de créer un entier signé) { if (il en veut un de taille de 1 byte) instanciation de la structure avec int8 if (il en veut un de taille de 2 bytes) instanciation de la structure avec int16 ... } if (utilisateur a choisit de créer un entier non signé) { f (il en veut un de taille de 1 byte) instanciation de la structure avec uint8 if (il en veut un de taille de 2 bytes) instanciation de la structure avec uint16 ... } pareil pour le type enum, float, bool...
En fait, un code proche de usign unNom = unType; ne fonctionne qu'en C++11, mais est équivalent (avec quelques joyeusetés en plus) à typdef unType unNom;Evidemment, pour pouvoir utiliser cette fonctionnalité, il faut que le compilateur la supporte (et je ne sais pas du tout si c'est le cas de Visual Studio, par exemple :aie:)
Mais, mis à part cette particularité, tout ce que j'ai expliqué jusqu'ici reste tout à fait valable, même avec un compilateur qui ne supporte que l'ancienne norme, et l'utilisation des template a, justement, pour but de t'éviter d'avoir à tester ce que le compilateur connait bien mieux que toi.
Si la structure sous la forme de
refuse de compiler à cause de la directive usign, ce n'est pas bien grave, travaillons avec des typedef, et essayons sous la forme deCode:
1
2
3
4
5
6
7
8
9
10 template <typename Type> struct Data{ /* ca, c'est juste pour pouvoir récupérer le type en cas de besoin ;) */ usign value_type = Type ; enum{accessSize = sizeof(Type)}; // la taille réellement utilisée en mémoire par Type (en bytes ;) ) value_type original; // la valeur d'origine (dans son type d'origine) unsigned char transformed[sizeof(uint64_t)];//la représentation en 64 bits /** lsb et tous les autres */ };
(dans le cas présent, j'essayais juste d'éviter le recours au typedef car il a été déprécié, ce qui signifie que l'on peut s'attendre, à terme, à ce qu'il soit purement et simplement retiré ;) :P)Code:
1
2
3
4
5
6
7
8
9
10 template <typename Type> struct Data{ /* ca, c'est juste pour pouvoir récupérer le type en cas de besoin ;) */ typedef Type value_type; enum{accessSize = sizeof(Type)}; // la taille réellement utilisée en mémoire par Type (en bytes ;) ) value_type original; // la valeur d'origine (dans son type d'origine) unsigned char transformed[sizeof(uint64_t)];//la représentation en 64 bits /** lsb et tous les autres */ };
Mais un code proche de
te convaincra sans doute que c'est bel et bien ce que tu souhaites ;)Code:
1
2
3
4
5
6
7 int main(){ Data<char> dc; // dc::value_type = char , dc::accessSize = 1 Data<short> ds; // ds::value_type = short, dc::accessSize = (sans doute) 2 Data<int> di; // di::value_type = int, dc::accessSize = (sans doute) 4 std::cout<< dc.accessSize <<" "<<ds.accessSize <<" "<<di.accessSize <<std::endl; }
Ok super !
Tu me conseilles donc de faire concorder l'acessSize avec la vrai taille du type alors et ca lors de la création de la donnée (si j'ai bien compris:oops:)
Il me faudra toujours ma batterie de if-else if mais au moins ce sera fait, c'est ca ?
Et la j'aurai plus qu'à utiliser :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 if (utilisateur a choisit de créer un entier signé) { if (il en veut un de taille de 1 byte) Data<int8_t> dc; if (il en veut un de taille de 2 bytes) Data<int16_t> dc; ... } if (utilisateur a choisit de créer un entier non signé) { if (il en veut un de taille de 1 byte) Data<uint8_t> dc; if (il en veut un de taille de 2 bytes) Data<uint16_t> dc; ... }
Et tout sera réglé. (Enfin j'espère :aie:)Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 template <class T> void read(T& val, const unsigned char* data, int msb, int lsb, bool big_endian=true) { const size_t size = sizeof(T); typedef typename unsigned_<size>::type type; type *ptr = reinterpret_cast<type*>(&val); for(size_t i=0; i<size; ++i) ptr[big_endian ? size-i-1: i] = data[i]; val <<= (sizeof(val) * CHAR_BIT - msb - 1); val >>= lsb; } //et de l'appeler comme cela : read(dc.original, ...);
oui, tout à fait
Mais, justement, non!!!Citation:
Il me faudra toujours ma batterie de if-else if mais au moins ce sera fait, c'est ca ?
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 if (utilisateur a choisit de créer un entier signé) { if (il en veut un de taille de 1 byte) Data<int8_t> dc; if (il en veut un de taille de 2 bytes) Data<int16_t> dc; ... } if (utilisateur a choisit de créer un entier non signé) { if (il en veut un de taille de 1 byte) Data<uint8_t> dc; if (il en veut un de taille de 2 bytes) Data<uint16_t> dc; ... }
Tu n'as absolument pas besoin de faire cette batterie de tests!!!
Si tu compiles le code de mon intervention précédente, tu te rendras compte que la taille indiquée correspond bel et bien au type que l'utilsateur a décidé duiliser
En fait, je te conseille carrément de ne transmettre que la structure, en un seul paramètre, et donc sous une forme proche deCitation:
Et la j'aurai plus qu'à utiliser :
Et tout sera réglé. (Enfin j'espère :aie:)Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 template <class T> void read(T& val, const unsigned char* data, int msb, int lsb, bool big_endian=true) { const size_t size = sizeof(T); typedef typename unsigned_<size>::type type; type *ptr = reinterpret_cast<type*>(&val); for(size_t i=0; i<size; ++i) ptr[big_endian ? size-i-1: i] = data[i]; val <<= (sizeof(val) * CHAR_BIT - msb - 1); val >>= lsb; } //et de l'appeler comme cela : read(dc.original, ...);
Ce sera bien plus facile, et bien plus efficace ;)Code:
1
2
3
4 template <typename T> void read(Data<T> /* const */ & data){ /* tu utilise les membres de data ici */ }
Désolé koala01 de répéter ça mais je suis obligé de faire cette batterie de tests !
Quand l'utilisateur crée une donnée, il le fait via une GUI qui ressemble à Excel : chaque ligne est une donnée et chaque colonne une info (nom de la donnée, accessSize, type, valeur, lsb, toussa toussa).
Pour l'accessSize c'est une comboBox (1, 2, 4, 8) et pour le type aussi (bool, enum, entier signé, entier non signé).
Une fois qu'il valide, en interne je me retrouve avec pleins d'informations par ligne que je dois stocker et il faut bien que je fasse le lien entre le type et la taille pour en déduire le type T pour lui assigner la valeur.
Par exemple, si il a choisi type = entier signé et taille d'accès 4 bytes bah je fais Data<int32_t> dc; et hop je remplis la structure !
Enfin je vois pas comment faire autrement :aie:
:question:
Une dernière réponse et je serai fixé :calim2:
Non en fait je mens :roll: J'ai juste besoin d'une dernière confirmation
Si on prend la structure que koala01 m'a conseillé d'utiliser. J'aimerai savoir si je peux faire une liste de cette structure (std::vector, QVector, etc..) ? Car étant de taille inconnue (la structure hein) je me demande comment cela peut marcher !
En fait, on travaille ici sur l'aspect "business" uniquement, ou, si tu préfères, sur le coté "modèle" de ton application.
Je crois déjà avoir insisté auprès de toi sur l'impérative nécessité de séparer clairement la partie métier de la partie purement "représentative" pour l'utilisateur et de la partie qui s'assure que l'utilisateur ne fait pas de conneries avant d'envoyer ses ordres à la partie métier.
au niveau de ta fonction d'encodage, tu n'as absolument pas à t'inquiéter d'autre chose que du type effectif, et tu n'as donc absolument pas besoin (au niveau de la partie métier du moins) de commencer à faire le moindre test pour savoir ce qui correspond le mieux ;)
Par contre, au niveau de l'IHM, c'est l'utilisateur lui-même qui devrait décider du type réel de la donnée qu'il veut encoder, au moment où il construit sa table.
Pour la création de la table, tu devrais donc fournir un "combobox" qui reprend les différents types possible (pour l'instant, on va dire les 13 types primitifs).
Sur base de cette information (fournie par l'utilisateur!!!), tu n'auras même pas à t'inquiéter de la taille de la donnée, tu n'auras qu'à... indiquer clairement le type qui spécialise la structure en fonction de l'index sélectionné du combobox.
Je m'explique:
Tu devrais fournir un combobox qui donne le choix entre char, unsigned char, short, unsigned short, int, unsigned int et tous les autres.
Tu pourrais alors avoir un simple switch case qui fonctionne sous la forme de
Et le controleur devrait, au moment où l'utilisateur remplit la table, s'assurer que la donnée introduite par l'utilisateur correspond bel et bien au type que ta table attend pour un champs donné.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 switch( selectedIndex){ case 1: Data<char> d; /* transmission à un système template qui l'utilise */ break; case 2: Data<unsigned char> d; /* transmission à un système template qui l'utilise */ break; case 3: Data<short> d; /* transmission à un système template qui l'utilise */ break; /* et ainsi de suite */ }
Les IHM vont, en effet, essentiellement travailler sur des valeurs sous forme de chaines de caractères.
Il faudra donc que tu vérifies si la valeur représentée par cette chaine de caractères correspond bel et bien au type qui est attendu pour le champs particulier de la table pour lequel l'utilisateur est sensé donner une valeur.
Le type (unsigned) char excepté (car il serait plutôt représenté par le caractère ASCII (ou un caractère de tout autre jeux de caractères) que par une valeur numérique), tu devras vérifier si la valeur entre dans l'intervalle correspondant au type de la donnée.
Tu devras alors commencer les vérification en testant la présence d'un ., d'un E ou d'un e dans la chaine de caractères, afin de savoir si l'on part du principe qu'il s'agit d'un réel ou d'un entier.
S'il s'agit d'un entier, tu peux le convertir d'office en long long (le plus grand type primitif capable de représenter des valeurs entières), et s'il s'agit d'un réel, tu peux le convertir d'office en long double (le plus grand type primitif capable de représenter des valeurs réelles).
La dernière vérification à effectuer, une fois la conversion effectuée, sera alors de s'assurer que la valeur obtenue entre bel et bien dans l'intervalle de valeurs admises par ton champs (par exemple, que la valeur obtenue est bel et bien comprise entre 0 et 65535 (de tête) pour un unsigned short) ;)
La chose importante à retenir au niveau des template, c'est que A<B> est d'un type totalement différent de A<C>, sauf si la classe template A hérite d'une classe qui n'est pas template.
Tu peux donc, en effet, créer un std::vector<A<B>>, un autre std::vector<A<C>> et un troisième std::vector<A<D>> (ou leur équivalent QVector), mais tu ne peux pas créer un std::vector<A *> pour mettre toutes les valeurs ensembles.
A moins, bien sur, que A n'hérite d'une classe (S) non template.
Mais, si tu le fait, il faut savoir que tu devras créer un std::vector<S*> et que tu ne connaitra alors ta valeur que comme étant un S. Seul le polymorphisme "classique" (au sens OO du terme) pourra alors te venir en aide ;)
Merci koala01 et tous les autres qui m'ont bien éclairé et accepté de me répondre.
Je vais essayer de digérer ce que tu m'as dit koala01 et essayer d'en tirer le plus possible :ccool:
J'espère ne jamais vous revoir :aie: mais comme chacun le sait on est plus qu'on le croit confronté à des problèmes qu'il est souvent intéressant de partager. Et sur ce forum c'est très agréable de le faire :zoubi: