vilain cast

Le
Amerio
Bonjour,
Ce qui suit risque de choquer les plus jeunes, je prefere prévenir ;-)

Soit deux struct :
struct A {};
struct AA : A { }; // AA derive de A

Soit les deux classes:
class Test
{
virtual void handleA(A& a) { . }
}
typedef void(Test::*phandle_t)(A&);

class TestBis : public Test
{
virtual void handleAA(AA& aa) { . }
}


Et maintenant les vilains casts ! (musique de film d'horreur en fond)
int main()
{
A a;
AA aa;
Test test;
TestBis testbis;

phandle_t paa = reinterpret_cast<phandle_t>( &TestBis::handleAA );

(testbis.*paa)(aa);

return 0;
}

Mettons de coté l'aspect peu ragoutant de la chose
Mes questions :
A& et AA& ont-ils la meme taille (deux references, donc meme taille que deux
pointeurs ?)
Le reinterpret_cast est il portable (Intel/PowerPC) (VC++7.1/gcc3) ?
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Matthieu Moy
Le #736910
"Amerio"
Mettons de coté l'aspect peu ragoutant de la chose...


;-)

En C++, il y a un moyen conceptuellement bien plus propre que des
pointeurs vers des fonctions : Les fonctions-classes (on dit parfois
aussi foncteurs). Le principe est d'avoir une classe vide qui
redéfinie l'opérateur "()". Vu qu'on utilise le concept de classe,
c'est très flexible, on peut faire de l'héritage, un peu tout ce qu'on
veut, quoi ...

--
Matthieu

Fabien LE LEZ
Le #736909
On Wed, 07 Apr 2004 11:12:38 GMT, "Amerio"
Mettons de coté l'aspect peu ragoutant de la chose...


Bon, OK, on va supposer que l'idée d'utiliser ce genre de techniques
ne t'es jamais venu à l'esprit.

[NB : je ne m'y connais pas assez dans ce domaine pour que ce qui suit
ait la moindre valeur de référence...]

Mes questions :
A& et AA& ont-ils la meme taille (deux references, donc meme taille que deux
pointeurs ?)


AMHA, parler de la taille de A& n'a pas vraiment de sens. On ne peut
que parler de la taille du pointeur sous-jacent. Et c'est
effectivement la même.

Le reinterpret_cast est il portable (Intel/PowerPC) (VC++7.1/gcc3) ?


Je crois que le reinterpret_cast<> est valide, car les types de départ
et d'arrivée ont la même taille, et le principe de reinterpret_cast<>
est de copier des bits d'une variable à l'autre.
Maintenant, la seule manière dont je l'utilise est de stocker
temporairement un pointeur dans un entier (ou un truc dans ce
goût-là). En gros, j'ai un pointeur, je le transforme en un entier,
puis je le retransforme en un pointeur du même type que l'original
avant de l'utiliser :

void f (unsigned n)
{
cout << *reinterpret_cast<std::string*>(n) << endl;
}

void g()
{
std::string s ("Hello World!");
f (reinterpret_cast<unsigned>(&s));
}

Ça non plus, c'est pas très ragoutant, mais je sais que ça marche, du
moins sous Win32 -- et comme je n'utilise le mécanisme en question
qu'à cause du mode de fonctionnement de cette plate-forme, tout va
bien ;-)

Donc, en conclusion, le reinterpret_cast<> me paraît correct. Ce qui
ne veut pas dire que la variable ainsi créée ait la moindre utilité.
Pour la validité de la ligne suivante (l'utilisation de la variable en
question), j'ai de gros doutes, mais je préfère laisser la parole à
quelqu'un de plus au fait que moi de ces choses-là...

--
;-)

kanze
Le #715991
Fabien LE LEZ news:
On Wed, 07 Apr 2004 11:12:38 GMT, "Amerio"
Mettons de coté l'aspect peu ragoutant de la chose...


Bon, OK, on va supposer que l'idée d'utiliser ce genre de techniques
ne t'es jamais venu à l'esprit.


Si ce n'était que ragoutant...

[NB : je ne m'y connais pas assez dans ce domaine pour que ce qui suit
ait la moindre valeur de référence...]

Mes questions :
A& et AA& ont-ils la meme taille (deux references, donc meme taille
que deux pointeurs ?)


AMHA, parler de la taille de A& n'a pas vraiment de sens. On ne peut
que parler de la taille du pointeur sous-jacent. Et c'est
effectivement la même.


« You should have quit when you were ahead »:-). Ta première phrase est
correcte, il n'y a pas de sens de parler de la taille des références,
parce qu'elles n'en ont pas. Il est probable (même très probable) que le
compilateur utilise des pointeurs dans l'implémentation des références,
mais c'est un détail interne, et rien ne dit que de tels pointeurs
ressemblent à d'autres pointeurs.

Le reinterpret_cast est il portable (Intel/PowerPC) (VC++7.1/gcc3) ?



Un reinterpret_cast est quasiment par définition non portable. Il y a
vraiment peu de garanties : on peut convertir un pointeur T* en void*
(ou en char*), et de retour en T* (le type d'origine), sans perte de
valeur. Ou éventuellement entre des pointeurs à des struct éléments d'un
union, pour accéder uniquement à une partie initiale identique.

Je crois que le reinterpret_cast<> est valide, car les types de départ
et d'arrivée ont la même taille, et le principe de reinterpret_cast<>
est de copier des bits d'une variable à l'autre.


Le reinterpret_cast permet de régarder un object comme s'il avait un
autre type. Dans la pratique, il n'a de l'utiliter que de ou vers
char*/void*. Où éventuellement, de façon bien non portable, vers ou de
des pointeurs à d'autres types entiers non-signés.

Maintenant, la seule manière dont je l'utilise est de stocker
temporairement un pointeur dans un entier (ou un truc dans ce
goût-là).


Je m'en suis servi (ou plutôt de son équivalent moral en C) dans
l'implémentation des fonctions modf, frexp et ldexp, pour accéder
directement aux champs de l'exposant et de la mantisse des double. Le
code n'était valable que pour des machines avec des flottants IEEE, et
comportait aussi des dépendences sur l'ordre des octets. (C'est la seule
fois en plus de trente ans de programmation que j'ai écris du code qui
dépendait de l'ordre des octets.)

En gros, j'ai un pointeur, je le transforme en un entier, puis je le
retransforme en un pointeur du même type que l'original avant de
l'utiliser :

void f (unsigned n)
{
cout << *reinterpret_cast<std::string*>(n) << endl;
}

void g()
{
std::string s ("Hello World!");
f (reinterpret_cast<unsigned>(&s));
}


C'est beau, mais ça marche pas sur ma machine. Un pointeur, c'est bien
64 bits, tandis qu'un unsigned, il n'y en a que 32.

Ça non plus, c'est pas très ragoutant, mais je sais que ça marche, du
moins sous Win32 -- et comme je n'utilise le mécanisme en question
qu'à cause du mode de fonctionnement de cette plate-forme, tout va
bien ;-)


Tu dois ne compiler qu'en modèle small, aussi. (Ou est-ce que c'est le
seul modèle supporté maintenant ? Dans le temps, j'ai bien travaillé en
modèle compact sur un 80386, avec des pointeurs de 48 bits, et des long
de 32 bits. Aucun type entier n'aurait fait l'affaire.)

Mais c'est juste pour dire, ou pour la culture générale. Dans la mésure
que tu sais les limitations (ce qui semble être le cas), et qu'il y a
une contrainte externe...

Donc, en conclusion, le reinterpret_cast<> me paraît correct.


Ça m'étonnerait. Il se peut qu'il marche, ou qu'il ait l'aire de
marcher, mais c'est un comportement indéfini, et je n'aurais pas de mal
à faire en sorte qu'il ne marche pas. Le fait que deux types ait la même
taille ne suffit pas qu'on peut s'en servir d'un à la place de l'autre.

Le problème vient en fait de l'utilisation du pointeur ainsi converti :

(testbis.*paa)( aa ) ;

Le compilateur est convaincu qu'il doit appeler un Test avec un A&. Il
effectue donc les conversions nécessaire -- il appelle la fonction avec
un pointeur this qui désigne la partie Test de testbis (et non l'objet
entier), et il convertit aussi le paramètre en pointeur de base. Dans
les cas les plus simple, il arrive dans beaucoup d'implémentations que
l'adresse de la partie classe de base soit la même que l'adresse du
dérivé, et ça aurait l'aire de marcher. Mais ce n'est pas garanti, et
dès que le code se complique un peu, ça cesse d'être vrai dans la
pratique aussi. Alors, il appelera la fonction de TestBis avec un
pointeur this et/ou une référence AA qui ne désigne pas un TestBis ou un
AA. Et alors, c'est cuit. On ne sait pas ce qui pourrait se passer.

Ce qui ne veut pas dire que la variable ainsi créée ait la moindre
utilité. Pour la validité de la ligne suivante (l'utilisation de la
variable en question), j'ai de gros doutes, mais je préfère laisser la
parole à quelqu'un de plus au fait que moi de ces choses-là...


D'accord. On est bien d'accord alors.

Formellement, je ne crois pas qu'il y ait une garantie explicite dans la
norme que tous les pointeurs à des fonctions membres soit convertible.
Dans la pratique, il y a des cas (assez rares) où on a besoin de
l'équivalent d'un void* pour un pointeur à fonction membre --
typiquement, on utilise quelque chose du genre void (T::*pfm)(). Et
garantie ou non, il est impensable à ce qu'un compilateur casse du code
qui convertir un pointeur à fonction membre vers un autre pointeur à
fonction membre, à condition qu'il le réconvertit vers le type du départ
avant de s'en servir.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Fabien LE LEZ
Le #715554
On 7 Apr 2004 23:59:58 -0700, wrote:

C'est beau,


Non.

mais ça marche pas sur ma machine.


Normal, c'est prévu pour Windows 32 bits, et plus précisément ses
fonctions SendMessage() et PostMessage(), et n'a pas le moindre
intérêt ailleurs.

--
;-)

Publicité
Poster une réponse
Anonyme