Dynamic_cast sans RTTI .... [C++] - Programmation
Marsh Posté le 07-03-2002 à 15:56:24
Encore toi
mais c'est affreux ca
Bon il me semble que tu as oublié les "virtual"
class B: virtual public A
{
};
Cela dit, j'ai pas trop pris la peine de lire...je m'y pencherai ce soir si j'ai le tmps !
Marsh Posté le 07-03-2002 à 16:01:10
Parce que si tu met pas virtual quand tu dérive tes classes ben, les données de la clase mère sont recopiées dans les classes filles. D'où ambiguité et problèmes.
Marsh Posté le 07-03-2002 à 16:05:35
Eh oui, c'est encore moi
Mais non, non pas de virtual avant l'héritage.
Je ne fais pas d'héritage multiple et je ne veux pas utiliser les RTTI.
Marsh Posté le 07-03-2002 à 16:13:06
ça m'étonnerait qu'il y ait une fonction c++ pour tester ce genre de trucs, car le standard ne spécifie rien pour l'implémentation des méthodes virtuelles, c'est juste que tout le monde fait des vtables. maintenant, y'a peut-être des 'microsoft specific' ...
mais à moins de créer une 'factory' qui s'occupe de créer les objets et d'enregister leur vtable, je vois pas bien.
pourquoi tu n'implémentes pas un isKindOf() à la main ? c'est tout simple ...
Marsh Posté le 07-03-2002 à 16:15:34
merde heu forcément sans regarder le code
heu ca compile pas sans les RTTI? (warning? ou error?)
(je débarque)
Marsh Posté le 07-03-2002 à 16:17:00
Pourquoi ne programmes-tu pas en assembleur, ainsi tu aurrais tout loisir d'acceder a la vTable !
Pourquoi en C il faut mettre des accolabes plutot que des begin/end ?
Il a des question auxquelles la seule reponse est parce que...
Pourquoi-faut il RTTI pour utiliser le dynamic_cast : parce que !
Effectivement ta methode marche... Dans le cas que tu indique
Mais il est possible d'avoir des cas ou la vTable serait la meme, par une optimisation du compilo et parce que la classe heritiere ne redefinit aucune fonction...
Maintenant si ta question est plutot je n'ais pas acces au RTTI (parce compilo ancien par ex), et bien oui il existe des mthodes pour emuler le dynamic_cast, ces methodes repose sur un pricinpe introdure dans les classes des attributs decrivant la classe.
Par exemple
ajoute virtual A* getA();
virtual B* getB();
virtual C* getC();
dans A, B et C puis ces methodes retourneront NULL ou this en fonction de la classe dans laquelle elle sont implementes...
Et tu as un comportement comme dynamic_cast<>, que je n'utilise personnelement peu preferant la methode citee plus haut, qui si elle est lourde montre au lecteur du code que je me trouve dan,s le besoin de down_caster, ce qui est suffisement inhabutuel a mon sens pour que le fait soit fait explicitement au moment de conception...
Marsh Posté le 07-03-2002 à 16:20:44
avec la stl, tu as typeid qui renvoie un type_info...
houlala oui, je suis à la masse, j'avais pas lu ton post, my mistake!!
Marsh Posté le 07-03-2002 à 16:23:34
- Willythekid : sans RTTI ça compile avec un warning mais à l'execution ça plante.
- youdontcare : c'est un isKindOf() par classe ou un isKindOf globale ? Tu pourrais me donner un chtite exemple ?
Marsh Posté le 07-03-2002 à 16:27:30
je reste partisan du isKindOf().
classe de base :
class A
{
virtual bool isKindOf(char* str) { return !strcmp(str, "A" ); }
};
puis pour les classes dérivées :
class B : public A
{
virtual bool isKindOf(char* str) { return !strcmp(str, "B" ) ? true : A::isKindOf(str); }
};
class C : public A
{
virtual bool isKindOf(char* str) { return !strcmp(str, "C" ) ? true : A::isKindOf(str); }
};
ce qui peut se ranger dans une jolie macro
PARCE QUE !
donc ensuite :
A* obj1 = new B();
A* obj2 = new C();
B* b = null;
C* c = null;
if (obj1->isKindOf("B" )) b = (B*)obj1;
if (obj2->isKindOf("C" )) c = (C*)obj2;
méthode utilisée dans max, les mfc, unreal, half life, j'en passe et des meilleures.
[jfdsdjhfuetppo]--Message édité par youdontcare--[/jfdsdjhfuetppo]
Marsh Posté le 07-03-2002 à 16:43:57
youdontcare > chacun sa methode...
Je prefere la mienne qui enleve le down-cast...
Parce que les down_cast c'est mal
Marsh Posté le 07-03-2002 à 16:46:48
>> chacun sa methode...
>> Parce que les down_cast c'est mal
ta méthode implique de changer toutes les classes dès qu'on en rajoute une. ça aussi c'est mal
Marsh Posté le 07-03-2002 à 16:46:50
merci youdontcare, c'est peu la même que je craignais. C'est vrai qu'elle est pas mal, mais elle a un petit defaut à mon gout :
En effet j'aurai aime que chaque personne codant une classe n'est pas à retaper la fonction IskindOf.
Tu parlais d'une macro, je suis super interessé ! Je suis fan des macros
Es-ce que tu pourrais m'expliquer ?
Parce que si mon code ressemblait à ça :
class C : public A
{
DEFINE_IFKINDOF()
};
je serais au anges ... voir même ça :
class C : public HERITAGE( A )
{
DEFINE_IFKINDOF()
};
j'accepterais
Sinon, Benb, t'as méthode ne peut pas me convenir, parce que je rajoute des classes en nombre indéfini.
Marsh Posté le 07-03-2002 à 16:55:58
dans un framework 'extensible', les macros sont hyper utilisées pour ce genre de trucs. dans half life (de mémoire), ça définit par ex isKindOf(), qq variables & méthodes qui s'occupent de sauvegarder la classe automatiquement, et plein d'autres trucs sympas. si tu veux te prendre une belle baffe niveau archi, choppe la sdk de half life
pour une macro, tu as au moins deux options :
class C : public A
{
DEFINE_ISKINDOF(C, A)
};
avec
#define DEFINE_ISKINDOF(C, BASE) \
virtual bool isKindOf(char* str) { return !strcmp(str, ##C) ? true : BASE::isKindOf(str); }
ou :
defineClass(C, A)
// tes méthodes ...
endClass()
avec
#define defineClass(C, BASE) \
class C : public BASE \
{ \
virtual bool isKindOf(char* str) { return !strcmp(str, ##C) ? true : BASE::isKindOf(str); }
#define endClass };
//
le "truc magique" ici, c'est ## (ou seulement # ? me souviens plus ..) qui transforme le paramètre en string.
Marsh Posté le 07-03-2002 à 16:56:33
on ne peut pas juste avoir
DEFINE_ISKINDOF()
car les macros sont quand même hyper limitées
Marsh Posté le 07-03-2002 à 17:02:37
ok merci pour tes macros, en effet j'avais pas penser à une macro toute bete avec des parametres...
C'est vrai que c'était évident.
D'ailleurs es-ce que tu masterises les macros ? Tu veux un problème bien tordu ?
Marsh Posté le 07-03-2002 à 17:07:41
leander a écrit a écrit : D'ailleurs es-ce que tu masterises les macros ? Tu veux un problème bien tordu ? |
pas des masses, mais je veux bien un problème tordu (avec suffisamment de contexte)
Marsh Posté le 07-03-2002 à 17:10:01
[citation][nom]youdontcare a écrit[/nom
ta méthode implique de changer toutes les classes dès qu'on en rajoute une. ça aussi c'est mal
[/citation]
ET entre deux maux il faut choisir le moindre...
Donc faire en fonction de la situation
leaner >
La methode IsKindOf peut-tres bien etre une methode de la classe mere et comparer la chaine argument a une chaine de la classe...
chaine initialisee dans le constructeur par exemple...
Sur les macros des operateur comme diese ou diese-diese peuvent t'aider a la construire, mais personnellement je n'aime pas les macros, c'est casse pieds dans le deboggage...
Marsh Posté le 07-03-2002 à 17:12:17
bon c'est un probleme que je me suis posé ce matin.
En fait, j'ai redefini le ASSERT afin qu'il soit un peu mieux que celui de Visual.
Et donc j'ai fais un ASSERT qui prends un nombre de parametre variable comme le printf.
Mais le problème c'est que j'arrive pas à mettre mon "__asm {int 3}" dans une macro pour que mon code s'arrete dans le cpp ou j'ai mis le assert. Tout simplement parce que le int 3 se trouve dans un fct inline et pas dans une macro. Hors comme les macros ont un nombre de parametre variables, c'est chaud...
Donc en fait, l'idée c'est de faire une macro ASSERT qui appele la fonction assert qui affiche le message et qui suivant le retour fait appel ou non au int 3.
donc un truc dans le genre (sans le int 3):
#define ASSERT fct_assert
bool fct_assert( const char* format ...)
{
...
}
Et donc faut que je modifie la macro "#define ASSERT fct_assert" pour qu'il y ai un int 3.
J'avais pensé à un truc dans le genre
#define ASSERT __asm{int 3} && fct_assert
Mais le problème c'est que l'opérateur || commence par évaluer de la gauche vers la droite. S'il évaluait dans l'autre sens ça me permettrait de faire marcher ma bidouille ! Parce que bien sur je n'ai pas le droit de mettre du code après le fct_assert puisque le preprocesseur va me mettre les parenthèse avec les arguments.
Donc peut-être qu'il faudrait que le retour de ma fct_assert soit une classe sur laquelle j'ai redefini un opérateur qui sera évaluer de la droite vers la gauche... Mais je ne sais pas si c'est possible...
C'est un peu tordu, mais s'il y en a qui aime ça
Marsh Posté le 07-03-2002 à 17:15:27
BENB a écrit a écrit : La methode IsKindOf peut-tres bien etre une methode de la classe mere et comparer la chaine argument a une chaine de la classe... chaine initialisee dans le constructeur par exemple... |
non, car le but de isKindOf() est de renvoyer true si la classe est d'un certain type
eg
class A;
class B : A;
class C : B;
A* a = new C();
a->isKindOf("A" ); // renvoie true
a->isKindOf("B" ); // renvoie true
a->isKindOf("C" ); // renvoie true
Marsh Posté le 07-03-2002 à 17:22:08
ca fait quoi __asm(int 3) ?
( J'ai pas d'accolades donc je mets des () )...
Marsh Posté le 07-03-2002 à 17:27:18
BENB > breakpoint
leander > pourquoi pas dans la fonction ?
bool fct_assert( const char* format ...)
{
int returnValue;
if (returnValue)
{
_asm ...
}
}
tu n'as qu'à remonter dans la callstack pour voir où ça a breaké ...
Marsh Posté le 07-03-2002 à 17:32:34
leander a écrit a écrit : bon voila mon problème. J'ai le code suivant : class A { public: virtual ~A() {} }; class B : public A { public: virtual ~B() {} }; class C : public B { public: virtual ~C() {} }; void main() { A* pA=new C; B* pB; C* pC; pB = dynamic_cast<B*>(pA); pC = dynamic_cast<C*>(pA); } Donc pour que ce code marche il faut compiler avec les RTTI. Hors en réfléchissant un peu, je me suis dit qu'il n'y avait pas besoin du RTTI pour connaitre le type d'une classe. En effet, la variable pA contient un pointeur vers la table virtuelle des fonctions. Donc normalement à l'execution, sans rajouter d'info tel que les RTTI, le programme devrait être capable de me dire si mon objet pA est "castable" en C*. En effet il suffit qu'il regarde si le pointeur de la table de fonction virtuelle de pA est égale à la table de fct virtuelle de C (qui est toujours la même quelque soit l'instance créer normalement). Et pour le cast en B*, c'est pareil, il regarde la table de fct virtuelle et le programme devrait pouvoir voir que mon pA est un C* et que donc, il peut donc le caster en B*, puisque B hérite de C. Donc, es-ce qu'il y a une fonction C++ qui permet de faire ce que je dis. Sachant que toutes mes classes ont des opérateurs virtuels et qu'elles ne font pas d'héritage multiple. |
Le C++ n'a jamais été fait pour se poser ce genre de question
Abstraction Abstraction Abstraction Abstraction
[jfdsdjhfuetppo]--Message édité par Tetragrammaton IHVH--[/jfdsdjhfuetppo]
Marsh Posté le 07-03-2002 à 17:32:41
youdontcare a écrit a écrit : non, car le but de isKindOf() est de renvoyer true si la classe est d'un certain type eg class A; class B : A; class C : B; A* a = new C(); a->isKindOf("A" ); // renvoie true a->isKindOf("B" ); // renvoie true a->isKindOf("C" ); // renvoie true |
maClasseMere::IsKindOf(std::string& testClasse)
( // c'est une accolade ouvrante
size_type result = Kind.find(testClasse+";" );
return (result != std::string::npos);
) // acolade fermante
dans le constructeur il suffit d'ajouter
Kind.append("MaClasseFille;" );
Marsh Posté le 07-03-2002 à 17:34:28
parce que si je met le int 3 dans le code de ma fonction, le deboggueur va aller dans le .h de la fonction puisqu'il ne remplace pas le code comme une macro (même si je mets un __forceinline).
Donc c'est pour cela, que j'essaye de faire macro + fonction
Marsh Posté le 07-03-2002 à 17:35:47
BENB a écrit a écrit : maClasseMere::IsKindOf(std::string& testClasse) ( // c'est une accolade ouvrante size_type result = Kind.find(testClasse+";" ); return (result != std::string::npos); ) // acolade fermante dans le constructeur il suffit d'ajouter Kind.append("MaClasseFille;" ); |
effectivement !
Marsh Posté le 07-03-2002 à 17:38:02
A la limite si tu veux faire un truc propre qui n'est pas basé sur des adresses mémoires (et donc avec des hypothèses cachées), tu peux faire ça :
class A
{
static enum TYPE_CLASSE {classA,classB,classC};
virtual TYPE_CLASSE retourneType() { return classA; }
public: virtual ~A() {}
};
Tu override retourneType pour chaque sous classe et c'est nettement plus simple. Et surtout, ton code ne devient pas illisible pour celui qui viendra derrière.
Marsh Posté le 07-03-2002 à 17:38:51
http://gcc.gnu.org/onlinedocs/gcc-3.0/cpp_3.html#SEC19
3.6 Variadic Macros
A macro can be declared to accept a variable number of arguments much as a function can. The syntax for defining the macro is similar to that of a function. Here is an example:
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
ça te conviendrais pas ça ?
[jfdsdjhfuetppo]--Message édité par youdontcare--[/jfdsdjhfuetppo]
Marsh Posté le 07-03-2002 à 17:53:42
- youdontcare :
pour les macros, je suis en train de tester sous visual... je vous tiens au courant, mais j'avais pas trouver ça dans la MSDN. J'avais surement pas assez chercher
- Tetragrammaton IHVH : c'est déjà comme ça que je fonctionnais. Pour enregistrer mes classes auprès d'une fabrique j'utilisais un système d'ID avec un enum. C'est donc comme ça que je pensais implémenter mon IsKindOf. Mais que ce soit un char* ou un ID, l'idée reste en effet la même.
En tout cas, comme toi, je préfère les IDs qui sont moins gourmand et plus rapide.
Marsh Posté le 07-03-2002 à 17:58:37
bon, j'ai l'impression que le compilateur de Visual C++ 6.0 ne supporte pas trop les macros à multiple arguments. En tout cas, les exemples de la page web ne marche pas !
Marsh Posté le 07-03-2002 à 18:04:10
leander a écrit a écrit : - Tetragrammaton IHVH : c'est déjà comme ça que je fonctionnais. Pour enregistrer mes classes auprès d'une fabrique j'utilisais un système d'ID avec un enum. C'est donc comme ça que je pensais implémenter mon IsKindOf. Mais que ce soit un char* ou un ID, l'idée reste en effet la même. En tout cas, comme toi, je préfère les IDs qui sont moins gourmand et plus rapide. |
Ouaip, "static enum" roulaizzzz comme on dit
Ca a l'avantage d'être facilement maintenable quand on ajoute une classe. Le seul stress pour les "RTTI-à-la-main" c'est de ne pas oublier d'overrider.
J'aime pas le principe du char* car on ne differencie pas d'un autre char* qui pourrait être une chaine par exemple.
Marsh Posté le 07-03-2002 à 18:08:55
bon, sinon je relance mon concours de macro avec un operateur capable d'evaluer de la droite vers la gauche...
Parce que Visual ne gère pas les multi parametres. Pour sans convaincre il suffit de regarder leur methode de trace dans leur .h de debug.
Marsh Posté le 07-03-2002 à 18:09:33
pour la macro si on suppose que T est un operateur qui s'evalue de droite a gauche on peut faire avec une variable :
(result && __asm(int 3) ) T result=assert_fct
Ce qui supose que = est prioritaire sur T, or
1 - le seul operateur surlequel = soit prioritaire est la virgule qui s'evalue de gauche a droite...
2 - les seuls operateurs qui s'evaluent de droite a gauche sont
les operatuers unaires !
les operateurs d'affectations =, +=, *=, etc...
Certes ceci pourrait etre une bonne nouvelle :
= result = assert_fct s'evaluant dans le bon sens, mais ces operateurs prenent a gauche une g-value ce que le resultat d'un && n'est pas...
Or un operateur s'evaluant de droite a gauche est le seul moyen (a mon avis) de realiser ce que tu souhaites, hormis les macros a nombre d'argument variables bien sur...
Marsh Posté le 07-03-2002 à 18:20:06
en effet, Benb c'est exactement ça mon problème !
Et il n'y a pas d'opérateur que je pourrais redéfinir sur une classe de cette façon :
(MaClass.m_bResult && __asm(int 3) ) T assert_fct
ou ma fonction assert_fct renverrai une instance de MaClass...
class MaClass
{
public :
void operateur T ( bool result );
bool m_bResult;
};
D'après ce que tu dis, c'est pas possible, mais bon, je pose quand même la question.
Marsh Posté le 07-03-2002 à 18:35:06
En C++ tu ne peut pas definir d'operateurs nouveaux, uniquement redefinir les actuels...
en C(ANSI 89) tu as un garentie d'ordre d'execution (tous de gche a dte sauf unaires et affectation), mais en C++ (ISO 98) tu n'as plus cette garentie (sauf pour &&, le ou etc), l'ordre d'execution depends de l'implementation...
Certes tu pourrais chercher cet ordre, en suppoosant qu'il soit different de celui du C et fixe... mais je doute qu'un editeur de compilo se soit amuse a faire cela, et les rares qui ne doivent pas suivre les regles du C doivent le faire en fonction de parametre d'optimisation. De plus je ne te conseille pas de t'amuser a utiliser une telle "fonctionnalite" qui pourrait changer d'une version de compilo a l'autre (sans parler d'un pb de portage)...
Quand a la creation d'operateurs, je ne sais pas comment cela est gere par les langages qui authorisent cela (Ada par ex) mais il y le Pb de la priorite et de l'ordre d'execution !
je resume
a = f(x) + g(x)
en C ansi 89 f est evaluee avant g, et en C++(ISO98) cela depend du compilo...
donc ce qui serait le plus proche serait
(result && __asm(int 3)) = result = fct_assert
fct_assert serait evalue, puis result recevrait sa valeur
puis (result && __asm(int 3)) serait evalue et recevrait la valeur de result... c'est tordu, tres tordu et ne marche pas car le terme de gauche du second = n'est pas une g-valeur...
Marsh Posté le 07-03-2002 à 18:41:14
Pardon
((result && __asm(int 3),result) = result = fct_assert
j'utilise l'operateur virgule donc
((result && __asm(int 3),result) est une g-value (je crois)
mais je n'ais pas compile...
Marsh Posté le 07-03-2002 à 18:54:06
tu n'es pas loin !!
chez moi
((result && true,result) = result = fct_assert
compil !!! Par contre, le fait de mettre __asm {int 3} à la place de true, ça plante à la compilation.
Marsh Posté le 07-03-2002 à 20:23:33
leander a écrit a écrit : tu n'es pas loin !! chez moi ((result && true,result) = result = fct_assert compil !!! Par contre, le fait de mettre __asm {int 3} à la place de true, ça plante à la compilation. |
Ca plante ?
quelle erreur ?
Marsh Posté le 07-03-2002 à 20:32:06
( b && __asm {int 3}, b ) = fct_assert();
main.cpp(60) : error C2059: syntax error : '__asm'
main.cpp(60) : error C2143: syntax error : missing '' before '{'
main.cpp(60) : error C2143: syntax error : missing ';' before '{'
main.cpp(60) : warning C4091: '' : ignored on left of 'int' when no variable is declared
main.cpp(60) : error C2143: syntax error : missing ';' before 'constant'
main.cpp(60) : error C2143: syntax error : missing ';' before '}'
main.cpp(60) : error C2143: syntax error : missing ';' before ','
main.cpp(60) : error C2059: syntax error : ''
Marsh Posté le 07-03-2002 à 23:10:36
La macro doit ressembler a ca
#define ASSERT bool b; \
((result && __asm{int 3}),result) = result = fct_assert
je ne vois pas vraiment le Pb sinon le fait que __asm ne renvoie rien...
#define ASSERT bool b; \
((result && (__asm{int 3},true)),result) = result = fct_assert
ou je remplace le __asm{int 3} par un operateur virgule
(__asm{int 3},true)... qui lui renvoie quelque chose....
bon deja au depart c'etait pas genial, maintenant c'est completement illisible.
Marsh Posté le 07-03-2002 à 15:16:33
bon voila mon problème. J'ai le code suivant :
class A
{
public: virtual ~A() {}
};
class B : public A
{
public: virtual ~B() {}
};
class C : public B
{
public: virtual ~C() {}
};
void main()
{
A* pA=new C;
B* pB;
C* pC;
pB = dynamic_cast<B*>(pA);
pC = dynamic_cast<C*>(pA);
}
Donc pour que ce code marche il faut compiler avec les RTTI. Hors en réfléchissant un peu, je me suis dit qu'il n'y avait pas besoin du RTTI pour connaitre le type d'une classe.
En effet, la variable pA contient un pointeur vers la table virtuelle des fonctions. Donc normalement à l'execution, sans rajouter d'info tel que les RTTI, le programme devrait être capable de me dire si mon objet pA est "castable" en C*.
En effet il suffit qu'il regarde si le pointeur de la table de fonction virtuelle de pA est égale à la table de fct virtuelle de C (qui est toujours la même quelque soit l'instance créer normalement).
Et pour le cast en B*, c'est pareil, il regarde la table de fct virtuelle et le programme devrait pouvoir voir que mon pA est un C* et que donc, il peut donc le caster en B*, puisque B hérite de C.
Donc, es-ce qu'il y a une fonction C++ qui permet de faire ce que je dis. Sachant que toutes mes classes ont des opérateurs virtuels et qu'elles ne font pas d'héritage multiple.
[jfdsdjhfuetppo]--Message édité par leander--[/jfdsdjhfuetppo]