Je ne connais pas typeof, mais je connais le préprocesseur. Comme le préprocesseur passe avant toute étape de compilation etc. il en résultera que tu auras typeof(machin) et non le type de machin. Je suppose qu'il agit au même titre que sizeof.
Cela fait plaisir de te revoir GurneyH
Sinon en effet, il y a moyen de ruser avec les typedef. D'ailleurs on peut aussi utiliser ceux de l'en-tête stdint.h.
mmhhh... n'est-ce pas un peu dangereux ? Pas de contrôle de type (quoique... typeof marche avec gcc mais pas portable), et puis il y a les effets de bords aussi...
Non, parce que le but est en fait de définir plusieurs fonctions prenant des types différents, mais d'uniformiser l'appel via une macro. Voici un exemple:
max.def
#ifndef MAX_DEF
#define MAX_DEF 1
#define MAX_PROTOTYPE(TYPE) TYPE max_ ## TYPE (TYPE a, TYPE b)
#define MAX_DEFINITION(TYPE) MAX_PROTOTYPE(TYPE) \
{ \
if ((a) > (b)) { \
return (a); \
} else { \
return (b); \
} \
}
#define MAX(a, b, TYPE) max_ ## TYPE ((a), (b))
#endif /* MAX_DEF */
#include <stdio.h>
#include <stdlib.h>
#include "max.h"
int
main (void)
{
printf ("%d\n", MAX (10, 20, int));
printf ("%f\n", MAX (10.50, 10.40, double));
return EXIT_SUCCESS;
}
Alors oui, je sais, cette fonction max est complètement inutile puisqu'elle pourrait-être implémentée via une simple macro, mais c'est juste pour l'exemple.
Le but est donc de masquer les appels à des fonctions différentes via la macro MAX et de simplifier les définitions des différentes fonctions via les macros MAX_PROTOTYPE et MAX_DEFINITION. Dans la réalité la validité des types est bel et bien assurée puisqu'on appel une fonction différente suivant le type des arguments
EDIT: ne pas essayer avec un type composé de deux mots comme longlong
...cette astuce que j'ai déjà lue maintes et maintes fois, est désormais claire (côté comment ça marche, comment la réadapter à mes besoins) pour moi très bonne démarche pour la séparation des fichiers, et aussi pour le choix simpliste de l'exemple. Très pédagogique ! Je crois que je vais me servir de cette astuce pour simplifier quelques codes que j'ai pu pondre le long de ce fil
Sinon, je me demande... cette astuce et les X-macros mélangées... y'a sûrement moyen d'en tirer quelque chose d'utile, nan ?
Je vais tâcher d'activer mes neurones aujourd'hui pour voir si je trouve un truc sympa à réinvestir
Décidément, ce fil est vraiment un bon échange, je trouve ! Ça part parfois en « sucette » ce qui permet de chopper de nouvelle idées ! Très dynamique et intéressant, et puis ça trolle pas - rare pour un tel sujet !
Sinon, il y a une question qui me trotte dans la tête depuis un petit temps concernant l'héritage. J'entends souvent dire qu'il y a deux techniques:
- la première est d'inclure dans la structure fille une structure mère;
- la seconde est de définir la structure fille exactement comme la mère et d'ajouter ses propres champs à la suite.
Alors, du moment qu'il n'y a qu'un niveau d'héritage, aucune ne me semble poser problème, mais la première me semble franchement pas pratique si l'on a une suite d'héritage comme Personnage -> Humain -> Mage -> Enchanteur. Imaginons qu'on ait le champs "vie" dans la structure Personnage, on doit donc faire, self->mere->mere->mere->vie ?
De même, si l'on a l'idée farfelue d'utiliser le polymorphisme, cela ne me semble pas pouvoir fonctionner puisque, dans le cas où l'on passe une structure parente comme ceci par exemple: mage_recupere_mana(self->mere), la fonction utilisera la vtable de la structure Mage alors que l'on souhaite qu'elle utilise celle de la structure Enchanteur...
Voilà je ne sais pas si j'ai été clair, mais si quelqu'un à un contre exemple je serais curieux de le voir
hem... on voit souvent, effectivement, des trucs du genre : "recopier la struct mère dans la fille en respectant l'ordre des champs, puis ajouter le reste (l'héritage)"... perso, je n'aime pas ce genre de pratique, c'est dangereux, on peut inverser des champs, tout ça ... enfin, sauf si on utilise correctement les X-macros comme je l'ai montré dans un de mes posts.
En ce qui concerne l'idée d'un pointeur de la fille vers la mère, hum ça ressemble du coup à une relation, et plus à de l'héritage... en effet, du coup, la mère "n'est plus dans la fille" (NDT : rien à voir avec les accouchements). Faudrait peut-être des mécanismes pour automatiser la recherche dans les parents genre : mécanisme récursif, avec contrôle du type de l'objet crée, et aussi recherche des relations que la classe possède (parenté, interfaces...). Ce mécanisme pourrait être appelé au niveau des classes, mmhhh... c'est 'achement intéressant, hihi, je vais réfléchir pour proposer une solution en dessin pour tout à l'heure (big-schéma on fait plus dans le tuto maintenant, on fait dans le scan-pédago-gribouillo-schéma uhuh)...
EDIT ! Je suis en train de penser ( eeeh oui, ça m'arrive) !... Il faudrait se réaccorder sur les "scopes" et sur le vocabulaire à employer : "une classe, c'est : ...", "un type de classe, c'est : ...". En clair, je propose qu'on songe tout doucement à la rédaction d'un cahier des charges musclé pour définir proprement notre implémentation objet
Faudrait peut-être des mécanismes pour automatiser la recherche dans les parents genre : mécanisme récursif, avec contrôle du type de l'objet crée, et aussi recherche des relations que la classe possède (parenté, interfaces...). Ce mécanisme pourrait être appelé au niveau des classes
Cela m'a l'air franchement compliqué, mais je veux bien voir ça
Citation : heizmann
hem... on voit souvent, effectivement, des trucs du genre : "recopier la struct mère dans la fille en respectant l'ordre des champs, puis ajouter le reste (l'héritage)"... perso, je n'aime pas ce genre de pratique, c'est dangereux, on peut inverser des champs, tout ça
Avec des macros, il y a moyen de s'en sortir sans risque. Si je reprends ton idée de polymorphisme avec une vtable sous forme de structure, cela donne par exemple:
On a donc deux structures: la structure personnage et la structure mage. La structure mage hérite de la structure personnage. Les deux fonctions personnage_touche et personnage_delete peuvent être qualifiée de "virtuelle" en ce sens que la fonction appelée dépend du type de la structure passée en argument (comme les structures sont de tailles différentes cela était nécessaire pour les "destructeurs").
Bon, l'aspect ennuyeux est qu'il faut passer par realloc après l'appel au constructeur de la structure mère puisque les deux structures n'ont pas la même taille. Dans le cas où la structure fille serait la mère d'une autre structure et que ces deux structures disposeraient de "fonctions virtuelles", il serait nécessaire d'agrandir également la vtable.
Histoire d'éviter les cast lors de l'appel aux "fonctions virtuelles", j'ai utilisé des pointeurs générique et j'effectue les cast en interne.
Pour résumer l'exemple, si un personnage se fait toucher il perd des dégats équivalent au nombre passé en argument à la fonction personnage_touche alors qu'un mage ne perd que les dégats diminué du dixième de son mana. Lors de l'appel à un "destructeur", ce dernier affiche s'il est celui de la structure personnage ou celui de la structure mage.
Voilà, tout ça pour dire que je ne suis pas certains qu'il soit possible de faire de même avec un héritage basé sur l'inclusion des structures parentes dans les structures filles
c'est archi simple en effet ! Et bourré de pragmatisme ! J'aime bien !!!
Bon, il reste encore du travail... en effet, le principal serait de voir ce qu'on pourrait bien faire d'une réaffectation temporaire de VTables en dynamique... mmhhh...
Je ne connais pas typeof, mais je connais le préprocesseur. Comme le préprocesseur passe avant toute étape de compilation etc. il en résultera que tu auras
typeof(machin) et non le type de machin. Je suppose qu'il agit au même titre que sizeof.
En y repensant, en fait, c'est assez logique. typeof(x) ne peut être interprété par le préprocesseur.
Citation
Alors, du moment qu'il n'y a qu'un niveau d'héritage, aucune ne me semble poser problème, mais la première me semble franchement pas pratique si l'on a
une suite d'héritage comme Personnage -> Humain -> Mage -> Enchanteur. Imaginons qu'on ait le champs "vie" dans la structure Personnage, on doit donc faire,
self->mere->mere->mere->vie ?
De même, si l'on a l'idée farfelue d'utiliser le polymorphisme, cela ne me semble pas pouvoir fonctionner puisque, dans le cas où l'on passe une structure
parente comme ceci par exemple: mage_recupere_mana
(self->mere), la fonction utilisera la vtable de la structure Mage alors que l'on souhaite qu'elle utilise celle de la structure Enchanteur...
Voilà je ne sais pas si j'ai été clair, mais si quelqu'un à un contre exemple je serais curieux de le voir
En fait, ce que tu dis ne peut pas marcher avec des pointeurs. Si on choisit d'utiliser un premier membre de type structure en tant que premier champ pour représenter l'héritage, alors ça ne doit pas être un pointeur. Sinon, ça fait un genre d'héritage virtuel...
Sinon, à propos de vos codes précédents, certaines chose me font un peu sursauter :
@GurneyH: typedeflonglongLong;
Cette instruction ne risque pas de poser problème, étant donné que le type long existe déjà ?
@taurre: if(!(self->vtable=malloc(sizeof*self->vtable)))
Normalement, il n'y a pas besoin d'une vtable différente par objet créé. Tous les objets d'un même type partagent la même vtable, codée statiquement dès la compilation ou alors dans une phase d'initialisation du programme.
L'avantage de faire autrement, c'est qu'on peut customiser la vtable d'un objet en particulier. Pourquoi pas, mais en pratique c'est pas vraiment utile à mon avis.
Sinon, on peut se passer de realloc, si on partage la phase de construction en deux: allocation en mémoire puis appel du constructeur ensuite s'il y en a un. L'avantage de séparer les deux choses c'est que le constructeur fils peut appeler le constructeur père sans problème et sans avoir à tricher avec realloc. En plus, ça nous ajoute en quelque sorte la notion de constructeur par défaut...
Normalement, il n'y a pas besoin d'une vtable différente par objet créé. Tous les objets d'un même type partagent la même vtable, codée statiquement dès la compilation ou alors dans une phase d'initialisation du programme.
Ah! Je n'y avais pas pensé! Merci beaucoup!
C'est vrai que pour chaque structure ont peut se contenter d'une vtable statique, cela nous bouge le problème de son éventuelle réallocation (et de son allocation tout court).
Citation : QuentinC 2
Sinon, on peut se passer de realloc, si on partage la phase de construction en deux: allocation en mémoire puis appel du constructeur ensuite s'il y en a un. L'avantage de séparer les deux choses c'est que le constructeur fils peut appeler le constructeur père sans problème et sans avoir à tricher avec realloc. En plus, ça nous ajoute en quelque sorte la notion de constructeur par défaut...
Ça par contre j'y avais pensé
Mais, en effet cela évite des réallocation en cascade dans le cas d'une suite d'héritage.
EDIT: je me rends également compte que les cast dans les destructeurs sont inutiles
Voilà ce qui arrive quand on lit trop vite... désolé, effectivement.
Citation
EDIT: je me rends également compte que les cast dans les destructeurs sont inutiles
ON peut aussi ajouter que si on n'a pas besoin d'afficher le message, alors on n'a pas besoin non plus de différencier les destructeurs puisque free sait ce qu'il y a à libérer.
Au fait ça m'amène à une question débile: pourquoi le C++ différencie-t-il delete et delete[] ? Quel est l'intérêt ? (Je ne parle pas de son utilisation mais bien de l'intérêt que ça a techniquement parlant... un bloc de mémoire est un bloc de mémoire)
@taurre: if(!(self->vtable=malloc(sizeof*self->vtable)))
Normalement, il n'y a pas besoin d'une vtable différente par objet créé. Tous les objets d'un même type partagent la même vtable, codée statiquement dès la compilation ou alors dans une phase d'initialisation du programme.
L'avantage de faire autrement, c'est qu'on peut customiser la vtable d'un objet en particulier. Pourquoi pas, mais en pratique c'est pas vraiment utile à mon avis.
Hum, c'est sûrement inutile si on ne veut pas aller trop loin, maiiiis... j'essaie justement de chercher ce en quoi ça pourrait être utile.
j'ai pas trouvé du temps pour continuer à coder, je vais tâcher de refaire des codes plus sains la prochaine fois.
Sinon @QuentinC 2 : votre avant dernier post est assez intéressant, je vais sûrement l'étudier en détail la prochaine fois que je code, justement.
Au fait ça m'amène à une question débile: pourquoi le C++ différencie-t-il delete et delete[] ? Quel est l'intérêt ?
delete fait plus que libérer la mémoire, il doit aussi appeler un(ou des) destructeur(s).
delete n'a aucun moyen de savoir si il est en présence d'un objet, ou d'un tableau d'objets.
C'est à nous de lui préciser en faisant l'appel soit avec [], soit sans.
J'ajoute aussi que delete va invoquer les destructeurs dans l'ordre inverse.
Par contre, pourquoi syntaxiquement séparer les deux alors que le compilateur peut déterminer le type de l'objet à la compilation, aucune idée non plus.
J'ajoute aussi que delete va invoquer les destructeurs dans l'ordre inverse.
Par contre, pourquoi syntaxiquement séparer les deux alors que le compilateur peut déterminer le type de l'objet à la compilation, aucune idée non plus.
...peut-être simplement pour obliger le codeur à produire du code... lisible ?
J'ajoute aussi que delete va invoquer les destructeurs dans l'ordre inverse.
Par contre, pourquoi syntaxiquement séparer les deux alors que le compilateur peut déterminer le type de l'objet à la compilation, aucune idée non plus.
...peut-être simplement pour obliger le codeur à produire du code... lisible ?
Je ne vois pas l'information apportée par le fait qu'on libère un tableau d'objets ou un objet.
L'élément important, c'est de lire qu'on libère la mémoire allouée.
@MaitreZur je ne suis pas assez spécialiste sur les compilos pour le dire... :⁻/ des contraintes de l'époque peut-être ??...
@quentin-fait-du-c : eh oui des passionnés faut lire plus haut (et avant aussi, y'a du code).
C'est avant tout une question de pédagogie pour l'objet, et comprendre les mécanismes de la POO... sans toutefois être "ébahi" par la gestion de l'OO par les LOO
Ouaip j'ai suivi tout le sujet, mais même si j'aime beaucoup comprendre les arcanes des trucs que je fais, le langage orienté objet je lui trouve beaucoup de complications pour peu de choses
Et pis j'préfère clairement le bas que le haut niveau (j'ai fait du Windev une fois, berk !!), alors je vous laisse creuser hein
Ouaip j'ai suivi tout le sujet, mais même si j'aime beaucoup comprendre les arcanes des trucs que je fais, le langage orienté objet je lui trouve beaucoup de complications pour peu de choses
Et pis j'préfère clairement le bas que le haut niveau (j'ai fait du Windev une fois, berk !!), alors je vous laisse creuser hein
Bah a priori, selon les dires de certains, l'objet du moment qu'on a : "encapsulation+héritage+polymorphisme", c'est bon après, on est un peu "libre" de définir les frontières de ce que l'on veut faire aussi. Par exemple en C++ on a les héritages multiples... D'autres langages préféreront implanter la notion d'interfaces moi perso pouvoir le "recoder" sans mots-clés je trouve que ça a son intérêt pédagogique.
En effet l'intérêt pédagogique n'est pas moindre ! J'ai du mal à me rendre compte, mais je pense qu'on parle de beaucoup de travail, beaucoup de RTFM et de RTFK&R
Quand aux notions d'héritage multiples, d'interfaces, les codes que j'ai à faire ne sont pas assez pointus pour que j'en ai vraiment l'usage, donc je préfère m'amuser avec des pointeurs et des préprocesseurs !!
Le préprocesseur est amusant mais attention il ne faut pas trop en abuser, et l'utiliser pour des usages comme ils sont préconisés dans les "codes bien écrits".
L'OO en C, c'est pas si compliqué à mettre en œuvre que ça avec GObject, je vous conseil de lire ça: (lien). D'ailleurs ça facilite grandement le déploiement d'applications Gtk/Gnome
- Il y a un chemin vers chaque sommet, même le plus haut -
L'OO en C, c'est pas si compliqué à mettre en œuvre que ça avec GObject, je vous conseil de lire ça: (lien). D'ailleurs ça facilite grandement le déploiement d'applications Gtk/Gnome
+1 ! mais GObject est dur à déchiffrer en ce qui concerne les mécanismes de l'implémentation POO en C... d'où l'idée de vouloir refaire une sorte de mini-GObject, pour le fun (et la péda, mais je me fais vieux, je radodododote )
mmhhh... n'est-ce pas un peu dangereux ? Pas de contrôle de type (quoique... typeof marche avec gcc mais pas portable), et puis il y a les effets de bords aussi...
Non, parce que le but est en fait de définir plusieurs fonctions prenant des types différents, mais d'uniformiser l'appel via une macro. Voici un exemple:
max.def
#ifndef MAX_DEF
#define MAX_DEF 1
#define MAX_PROTOTYPE(TYPE) TYPE max_ ## TYPE (TYPE a, TYPE b)
#define MAX_DEFINITION(TYPE) MAX_PROTOTYPE(TYPE) \
{ \
if ((a) > (b)) { \
return (a); \
} else { \
return (b); \
} \
}
#define MAX(a, b, TYPE) max_ ## TYPE ((a), (b))
#endif /* MAX_DEF */
#include <stdio.h>
#include <stdlib.h>
#include "max.h"
int
main (void)
{
printf ("%d\n", MAX (10, 20, int));
printf ("%f\n", MAX (10.50, 10.40, double));
return EXIT_SUCCESS;
}
Alors oui, je sais, cette fonction max est complètement inutile puisqu'elle pourrait-être implémentée via une simple macro, mais c'est juste pour l'exemple.
Le but est donc de masquer les appels à des fonctions différentes via la macro MAX et de simplifier les définitions des différentes fonctions via les macros MAX_PROTOTYPE et MAX_DEFINITION. Dans la réalité la validité des types est bel et bien assurée puisqu'on appel une fonction différente suivant le type des arguments
EDIT: ne pas essayer avec un type composé de deux mots comme longlong
Il y a que moi qui trouve ça très très sale de devoir coder la fonction dans une macro ?
Je trouve ça pas propre et dangereux !
C'est très bien pour ces mini fonctions mais honnêtement ce n'est pas viable pour du code plus compliqué !
Il y a que moi qui trouve ça très très sale de devoir coder la fonction dans une macro ?
Je trouve ça pas propre et dangereux !
Cela dépend de ce que tu entends par sale
Dans le mesure où la lecture est relativement facile et claire, je ne trouve pas que cela est "sale". D'autant qu'à mon sens il faut aussi voir le gain que cela apporte. Qu'est ce qui est mieux entre une multitude de définitions identiques et une seule définition confinée dans une macro? Pour moi c'est la macro sans aucun doute.
Sinon dangereux cela peut l'être oui, comme toute utilisation du préprocesseur. Maintenant, du moment qu'on est un tant soit peu consciencieux il y a moyen de s'en sortir sans problème
Citation : ChristopheG
C'est très bien pour ces mini fonctions mais honnêtement ce n'est pas viable pour du code plus compliqué !
Pourtant cela existe, par exemple les en-têtes queue.h et tree.h
Non mais j'ai ma réponse, je doit être le seul ...
Citation : Taurre
Cela dépend de ce que tu entends par sale
Dans le mesure où la lecture est relativement facile et claire, je ne trouve pas que cela est "sale". D'autant qu'à mon sens il faut aussi voir le gain que cela apporte. Qu'est ce qui est mieux entre une multitude de définitions identiques et une seule définition confinée dans une macro? Pour moi c'est la macro sans aucun doute.
Sinon dangereux cela peut l'être oui, comme toute utilisation du préprocesseur. Maintenant, du moment qu'on est un tant soit peu consciencieux il y a moyen de s'en sortir sans problème
J'ai dut mal a croire que cela soit souvent nécessaire de le faire. Tu a souvent besoin de faire 36 fonctions avec chaque types possibles et inimaginable ?
C'est très pratique, j'en convient, mais mise a part pour des bibliothèque générique, cela n'ai pas franchement courant de devoir utiliser beaucoup de types sur une même fonction. Et si tu veux pouvoir changer facilement avant compilation, tu peux utiliser un typedef. En général si tu réfléchis bien ton code, tu peux t'en passer (a part pour des cas comme des listes)
Citation : Taurre
Pourtant cela existe, par exemple les en-têtes queue.h et tree.h
Que ça existe ne veut pas dire que c'est propre pour autant. On peut tout trouver, le problème n'est pas là. Il est probable que dans ce cas c'étais la solution la moins mauvaise, mais je ne trouve toujours pas ça terrible.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.