On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
On Apr 4, 7:46 am, "Dominique Vaufreydaz" <Doms@invalid> wrote:
Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.
pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).
Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
James Kanze wrote:On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrou ve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateu r.
James Kanze wrote:
On Apr 4, 7:46 am, "Dominique Vaufreydaz" <Doms@invalid> wrote:
Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrou ve avec
des erreurs (et pas a chacune des executions) du genre.
pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).
Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateu r.
James Kanze wrote:On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrou ve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Ce qui veut
dire que la résolution dynamique trouve la version de la
fonction dans Thread. Et la norme dit que si la résolution
dynamique trouve une fonction virtuelle pûre, c'est un
comportement indéfini (même si la fonction a une définition).Notons que le meme mecanisme
fonctionne a merveille sous Visual Studio 2005.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateu r.
Note que dans l'exemple qu'il a posté en réponse à Fabien, il a
exactement ce problème : sa fonction Run() sera appelée avant
que la variable id sera initialisé, et même l'incrémentation
pourrait avoir lieu avant l'initialisation. (Fort peu probable à
cause du « sleep(10) », mais toujours possible. Et j'imagine
que dans l'application réele, il n'y a pas toujours un sleep(10)
au début de Run().)
Notons comme je l'ai ecrit que c'etait un test bidon montrant
le probleme. Normalement, je n'ecris pas ce genre de chose.
Je voulais aussi tester dans ce cas, si le comportement du compilo
changeait avec une variable membre accèder dans la fonction Run.
Vala, vala. Merci donc, j'ai changé l'API de ma classe Thread.
Demarre plus toute seul ;oP Au moins, j'aurais appris qqchose
aujourd'hui.
Note que dans l'exemple qu'il a posté en réponse à Fabien, il a
exactement ce problème : sa fonction Run() sera appelée avant
que la variable id sera initialisé, et même l'incrémentation
pourrait avoir lieu avant l'initialisation. (Fort peu probable à
cause du « sleep(10) », mais toujours possible. Et j'imagine
que dans l'application réele, il n'y a pas toujours un sleep(10)
au début de Run().)
Notons comme je l'ai ecrit que c'etait un test bidon montrant
le probleme. Normalement, je n'ecris pas ce genre de chose.
Je voulais aussi tester dans ce cas, si le comportement du compilo
changeait avec une variable membre accèder dans la fonction Run.
Vala, vala. Merci donc, j'ai changé l'API de ma classe Thread.
Demarre plus toute seul ;oP Au moins, j'aurais appris qqchose
aujourd'hui.
Note que dans l'exemple qu'il a posté en réponse à Fabien, il a
exactement ce problème : sa fonction Run() sera appelée avant
que la variable id sera initialisé, et même l'incrémentation
pourrait avoir lieu avant l'initialisation. (Fort peu probable à
cause du « sleep(10) », mais toujours possible. Et j'imagine
que dans l'application réele, il n'y a pas toujours un sleep(10)
au début de Run().)
Notons comme je l'ai ecrit que c'etait un test bidon montrant
le probleme. Normalement, je n'ecris pas ce genre de chose.
Je voulais aussi tester dans ce cas, si le comportement du compilo
changeait avec une variable membre accèder dans la fonction Run.
Vala, vala. Merci donc, j'ai changé l'API de ma classe Thread.
Demarre plus toute seul ;oP Au moins, j'aurais appris qqchose
aujourd'hui.
Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
Guillaume GOURDIN writes:Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
"Il n'y est pas de probleme qui ne puisse etre resolu par une indirection
de plus."
En gros, le modele template permet d'adapter le fonctionnement d'une clas se
en substituant des membres virtuels. Le modele strategie permet d'adapter
le fonctionnement d'une classe en lui donnant en parametre une autre clas se
contenant (uniquement?) les membres qui sont adaptables. Il est plus
complique, mais plus souple.
Guillaume GOURDIN <gour...@liw.fr> writes:
Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
"Il n'y est pas de probleme qui ne puisse etre resolu par une indirection
de plus."
En gros, le modele template permet d'adapter le fonctionnement d'une clas se
en substituant des membres virtuels. Le modele strategie permet d'adapter
le fonctionnement d'une classe en lui donnant en parametre une autre clas se
contenant (uniquement?) les membres qui sont adaptables. Il est plus
complique, mais plus souple.
Guillaume GOURDIN writes:Ce n'est pas forcément une bonne idée. C'est le vieux argument
du modèle stratégie contre le modèle template
Pourrais-je te demander ce que sont ces 2 modèles stp? Merci!
"Il n'y est pas de probleme qui ne puisse etre resolu par une indirection
de plus."
En gros, le modele template permet d'adapter le fonctionnement d'une clas se
en substituant des membres virtuels. Le modele strategie permet d'adapter
le fonctionnement d'une classe en lui donnant en parametre une autre clas se
contenant (uniquement?) les membres qui sont adaptables. Il est plus
complique, mais plus souple.
Si tu la modifies pour qu'elle utilise le modèle stratégie, tu
peux très bien utiliser le demarrage automatique dans le
constructeur. Parce que l'utilisateur serait amené
systèmatiquement à construire le délégué, c-à-d l'objet qui
s'exécute, avant de construire le Thread.
Si tu la modifies pour qu'elle utilise le modèle stratégie, tu
peux très bien utiliser le demarrage automatique dans le
constructeur. Parce que l'utilisateur serait amené
systèmatiquement à construire le délégué, c-à-d l'objet qui
s'exécute, avant de construire le Thread.
Si tu la modifies pour qu'elle utilise le modèle stratégie, tu
peux très bien utiliser le demarrage automatique dans le
constructeur. Parce que l'utilisateur serait amené
systèmatiquement à construire le délégué, c-à-d l'objet qui
s'exécute, avant de construire le Thread.
On Apr 5, 12:31 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Pas vraiment. Qu'on ait plusieurs niveaux d'héritage, et que les
constructeurs appellent des fonctions externes avec this comme
paramètre, et la polymorphisme devient évident.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Le pointeur, évidemment.
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
On Apr 5, 12:31 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
James Kanze wrote:
On Apr 4, 7:46 am, "Dominique Vaufreydaz" <Doms@invalid> wrote:
Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.
pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Pas vraiment. Qu'on ait plusieurs niveaux d'héritage, et que les
constructeurs appellent des fonctions externes avec this comme
paramètre, et la polymorphisme devient évident.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Le pointeur, évidemment.
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).
Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
On Apr 5, 12:31 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 4, 7:46 am, "Dominique Vaufreydaz" wrote:Je me suis dit que de toute facon, je ne peux
pas instancier la classe Thread (car abstraite du fait du
virtual ... = 0).
Le probleme qui se pose (sous g++ uniquement avec comme
flags de compilation
-Werror -Wall -pedantic -std=c++98 -fPIC) c'est que je me retrouve avec
des erreurs (et pas a chacune des executions) du genre.pure virtual method called
terminate called without an active exception
Abort
A priori, pas de debordement ailleurs (je verifie encore)
qui justifierait l'ecrasement de la table des fonctions
virtuelles. Des idées ?
C'est un comportement indéfini. Lors de la construction de
Thread, le type dynamique de l'objet est Thread.
De maniere generale, les objets polymorphiques deviennent monomorphiques
dans leurs constructeurs et destructeurs.
Pas vraiment. Qu'on ait plusieurs niveaux d'héritage, et que les
constructeurs appellent des fonctions externes avec this comme
paramètre, et la polymorphisme devient évident.
C'est probablement le résultat d'une optimisation. Le
compilateur, sachant que la fonction est pûre virtuelle, et ne
peut pas être appelée par la résolution dynamique dans le
constructeur, se passe d'initialiser le vtable pour la classe de
base, mais l'initialise immédiatement pour la classe dérivée.
La, je ne te suis pas. Quand tu parles "d'initialiser la vtable pour la
classe de base", tu parles de la table elle-meme ou du pointeur __vtbl
dans l'objet?
Le pointeur, évidemment.
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compilateur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compila teur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
Mais dans le cas present, le comportement est comme si le __vptr n'avait
pas ete change et donc cela ressemble a un bug. Certe, on peut
argumenter que c'est un comportement indefini comme un autre, mais je
prefere de loin le comportement de g++ qui ne laisse aucune ambiguite
sur le fait que le code est incorrecte.
Pour confirmer que c'est un bug, il faudrait trouver un code
correct qui n'a pas le comportement attendu.
James Kanze wrote:
On Apr 5, 12:31 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).
Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compila teur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
Mais dans le cas present, le comportement est comme si le __vptr n'avait
pas ete change et donc cela ressemble a un bug. Certe, on peut
argumenter que c'est un comportement indefini comme un autre, mais je
prefere de loin le comportement de g++ qui ne laisse aucune ambiguite
sur le fait que le code est incorrecte.
Pour confirmer que c'est un bug, il faudrait trouver un code
correct qui n'a pas le comportement attendu.
James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
Si on regarde une implémentation classique, on aurait quelque
chose du genre (en pseudo-C, mais avec les noms des fonctions du
C++) :
Base::Base()
{
__vptr = &Base::__vtable ;
// ...
} ;
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Mais si toutes les fonctions virtuelles de Base sont pûre, il
pourrait bien le transformer en :
Base::Base()
{
// ...
} ;
Derived::Derived()
{
__vptr = &Derived::__vtable ;
Base::Base() ;
// ...
} ;
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est une petite optimisation, une affectation de moins. Et dans
la mesure où des objets polymorphiques sont prèsque toujours
alloués dynamiquement, je me démande si ça vaut la peine. Mais
c'est toujours ça de gagner (et prèsque toujours, ce n'est pas
toujours -- il y a bien des variables locales polymorphiques
parfois, dans le modèle de visiteur, par exemple, et si l'objet
est très simple, ça peut faire une différence).Pour moi, la seule explication de ce comportement, c'est
l'absence d'ajustement de __vtbl dans le constructeur qui fait que
l'objet est du type Derivee au lieu d'etre du type Base et donc il
continue de voir les methodes de Derivee. C'est donc un bug du compila teur.
Ce n'est qu'un bug du compilateur que si on appelle une fonction
non-pûre implémentée dans Base, et qu'on exécute le code dans
Derived. Autant que Base ne contient pas de fonction virtuelle
non-pûre, c'est une optimisation tout à fait valable.
Mais dans le cas present, le comportement est comme si le __vptr n'avait
pas ete change et donc cela ressemble a un bug. Certe, on peut
argumenter que c'est un comportement indefini comme un autre, mais je
prefere de loin le comportement de g++ qui ne laisse aucune ambiguite
sur le fait que le code est incorrecte.
Pour confirmer que c'est un bug, il faudrait trouver un code
correct qui n'a pas le comportement attendu.
On Apr 10, 3:56 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'avoir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est intéressant. (On voit que tu y a mis du travail, à le
rendre compréhensible.)
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
On peut aussi arguer sur le plan « qualité
d'implémentation » ; tous les implémenteurs le prenent en
considération. Mais évidemment, c'est plus difficile ; ça ne
marche que quand on est d'accord sur ce qui est le comportement
souhaitable. (Certains semblent parfois vouloir cacher les
erreurs, et préfèrent que le programme donne un résultat erroné
et trompeur à ce qu'il crashe. Ou qu'il crashe plus tard. Ce qui
vaut pour les programmes de démo, dans les foires, où
l'observateur s'en fiche des résultats et n'a pas le temps de
les vérifier, et où plus tard signifie -- on l'espère, au
moins -- après que le client potentiel a déjà tourné le dos. En
dehors des foires, en revanche, je le trouve un peu
irresponsable.)
On Apr 10, 3:56 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
James Kanze wrote:
On Apr 5, 12:31 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'avoir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est intéressant. (On voit que tu y a mis du travail, à le
rendre compréhensible.)
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
On peut aussi arguer sur le plan « qualité
d'implémentation » ; tous les implémenteurs le prenent en
considération. Mais évidemment, c'est plus difficile ; ça ne
marche que quand on est d'accord sur ce qui est le comportement
souhaitable. (Certains semblent parfois vouloir cacher les
erreurs, et préfèrent que le programme donne un résultat erroné
et trompeur à ce qu'il crashe. Ou qu'il crashe plus tard. Ce qui
vaut pour les programmes de démo, dans les foires, où
l'observateur s'en fiche des résultats et n'a pas le temps de
les vérifier, et où plus tard signifie -- on l'espère, au
moins -- après que le client potentiel a déjà tourné le dos. En
dehors des foires, en revanche, je le trouve un peu
irresponsable.)
On Apr 10, 3:56 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'avoir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
Pour un cas un peu complique, tu peux
regarder le constructeur de la classe DCABBA dans:
http://cern.ch/laurent.deniau/html/cpp_object_model.tgz
fichier: object_model/DCABBA_layout.c
C'est du C qui compile et qui reproduit le modele objet de g++:
void DCABBA_ctor(register DCABBA* const this,
long a, long b, long c, long d,
long ab, long ba, long abba, long dcabba)
{
B_ctor(BASE(this,B), b); /* compiler + user */
A_ctor(BASE(this,A), a); /* compiler + user */
D_ctor((D*)&this->d, d); /* compiler + user */
C_ctor_((C*)this, DCABBA_vtt, b, c); /* compiler + user */
ABBA_ctor_(BASE(this,ABBA), DCABBA_vtt+2, a, b, ab, ba, abba);
/* compiler + user */
this->vptr = DCABBA_vtbl; /* compiler */
this->ABBA_vptr = DCABBA_ABBA_vtbl; /* compiler */
this->BA_vptr = DCABBA_BA_vtbl; /* compiler */
this->B_vptr = DCABBA_B_vtbl; /* compiler */
this->A_vptr = DCABBA_A_vtbl; /* compiler */
this->dcabba = dcabba; /* user */
print_layout("DCABBA", "ctor", this, sizeof *this);
}
Tu trouveras le code C++ beaucoup plus lisible dans object_model.cpp.
C'est intéressant. (On voit que tu y a mis du travail, à le
rendre compréhensible.)
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
On peut aussi arguer sur le plan « qualité
d'implémentation » ; tous les implémenteurs le prenent en
considération. Mais évidemment, c'est plus difficile ; ça ne
marche que quand on est d'accord sur ce qui est le comportement
souhaitable. (Certains semblent parfois vouloir cacher les
erreurs, et préfèrent que le programme donne un résultat erroné
et trompeur à ce qu'il crashe. Ou qu'il crashe plus tard. Ce qui
vaut pour les programmes de démo, dans les foires, où
l'observateur s'en fiche des résultats et n'a pas le temps de
les vérifier, et où plus tard signifie -- on l'espère, au
moins -- après que le client potentiel a déjà tourné le dos. En
dehors des foires, en revanche, je le trouve un peu
irresponsable.)
James Kanze wrote:On Apr 10, 3:56 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
[...]parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'av oir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
Je soupconnerais un cas d'inlining avec reorganisation des instructions
par l'optimiseur.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
A moins que le modele ait change depuis 2004 (date a laquelle j'ai ecrit
le debut de cet article), la distinction viendrait du passage de la
vtable en argument. Chaque constructeur a donc deux versions (cf ci-apres ).
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est bien ca, vtt = virtual table table (parce qu'il peut y en avoir
plusieurs). Ca permet de garder un contexte local pour C_ctor_ qui
decidera des affectations necessaires. X_ctor = constructeur, X_ctor_ =
constructeur avec vtt. Pour ABBA il y aussi un ajustement d'offset dans
la vtt. Tu peux aussi regarder DCABBA_ctor_ un peu plus loin dans le
meme fichier (section ctor partiels) pour voir la difference et les
implications.C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
Ce modele est celui decrit dans le papier de Nathan Sidwell de 2003 (la
ref est dans le html), ou du moins ce que j'en ai compris. J'ai essaye
de mettre en place ce que propose Natalie Eckel mais c'est vraiment trop
complique et n'apporte rien a l'aspect didactique du papier.
En fait ce papier est le resultat d'une discussion avec Gaby ici-meme ou
il m'avait appris que le seul moyen de respecter la norme etait de
reaffecter les vptr de this.
Comme j'avais lu que les hierarchies
complexes de classes polymorphiques pouvaient faire gonfler le code,
j'ai voulu voir les implications de ces reaffectations. Et j'ai trouve
que meme dans le cas de DCABBA qui est artificiellement compliquee
(inclue tous les cas possibles), les vtables annexes ne sont pas si
terrible que ca. Certe cette classe a 17 vtables et 1 vtt mais ca n'est
pas si gros que ca au final. Ou disont en tout cas que le modele objet
de g++ est un bon compromis d'optimisation entre espace et rapidite.
Par contre, une chose que je n'ai pas comprise, c'est pourquoi mon
implementation naive de dynamic_cast est trois fois plus rapide que
celle de g++, alors qu'il a acces a bien plus d'information (hierarchie
simple ou en diamant).
James Kanze wrote:
On Apr 10, 3:56 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
James Kanze wrote:
On Apr 5, 12:31 pm, Laurent Deniau <laurent.den...@cern.ch> wrote:
[...]
parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'av oir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
Je soupconnerais un cas d'inlining avec reorganisation des instructions
par l'optimiseur.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
A moins que le modele ait change depuis 2004 (date a laquelle j'ai ecrit
le debut de cet article), la distinction viendrait du passage de la
vtable en argument. Chaque constructeur a donc deux versions (cf ci-apres ).
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est bien ca, vtt = virtual table table (parce qu'il peut y en avoir
plusieurs). Ca permet de garder un contexte local pour C_ctor_ qui
decidera des affectations necessaires. X_ctor = constructeur, X_ctor_ =
constructeur avec vtt. Pour ABBA il y aussi un ajustement d'offset dans
la vtt. Tu peux aussi regarder DCABBA_ctor_ un peu plus loin dans le
meme fichier (section ctor partiels) pour voir la difference et les
implications.
C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
Ce modele est celui decrit dans le papier de Nathan Sidwell de 2003 (la
ref est dans le html), ou du moins ce que j'en ai compris. J'ai essaye
de mettre en place ce que propose Natalie Eckel mais c'est vraiment trop
complique et n'apporte rien a l'aspect didactique du papier.
En fait ce papier est le resultat d'une discussion avec Gaby ici-meme ou
il m'avait appris que le seul moyen de respecter la norme etait de
reaffecter les vptr de this.
Comme j'avais lu que les hierarchies
complexes de classes polymorphiques pouvaient faire gonfler le code,
j'ai voulu voir les implications de ces reaffectations. Et j'ai trouve
que meme dans le cas de DCABBA qui est artificiellement compliquee
(inclue tous les cas possibles), les vtables annexes ne sont pas si
terrible que ca. Certe cette classe a 17 vtables et 1 vtt mais ca n'est
pas si gros que ca au final. Ou disont en tout cas que le modele objet
de g++ est un bon compromis d'optimisation entre espace et rapidite.
Par contre, une chose que je n'ai pas comprise, c'est pourquoi mon
implementation naive de dynamic_cast est trois fois plus rapide que
celle de g++, alors qu'il a acces a bien plus d'information (hierarchie
simple ou en diamant).
James Kanze wrote:On Apr 10, 3:56 pm, Laurent Deniau wrote:James Kanze wrote:On Apr 5, 12:31 pm, Laurent Deniau wrote:
[...]parce que si toutes les fonctions virtuelles de Base sont pûre,
le compilateur sait qu'on ne les appelerait jamais à travers le
vtable.
L'inversion dans le dernier cas est futile a mon sens et je crois que
les choses sont assez compliquee pour que le compilateur reste sur une
methodologie simple et systematique.
Derived::Derived()
{
Base::Base() ;
__vptr = &Derived::__vtable ;
// ...
} ;
Fonctionne dans tous les cas et l'optimisation reste possible dans
Base::Base(). L'interet de cette optimisation, c'est qu'elle depend
uniquement du contexte local.
À vrai dire, c'est ce que j'aurais cru moi-même. En ayant
régardé le code généré, en revanche, je me rappelle bien d'av oir
vu un cas où l'initialisation s'est fait avant l'appel du
constructeur de Base.
Je soupconnerais un cas d'inlining avec reorganisation des instructions
par l'optimiseur.
En y refléchissant, je me démande s'il n'y avait d'héritage
virtuel. Il me sembe que je me serais souvenu si c'était le cas,
mais dans le cas d'héritage virtuel, on pourrait comprendre. Le
_vtable alors comprend des informations pour rétrouver la base
virtuelle, dont la position dépend de la classe finale la plus
dérivée. C'est donc à l'appelant d'initialiser le _vptr, et non
à l'appelé. (C'est la distinction, en g++, par exemple, entre
les deux points d'entrée des constructeurs, le « in charge »,
et l'autre. Un est appelé quand il s'agit de la classe la plus
dérivée ; il s'occupe de l'initialisation des classes de base
virtuelles et des _vptr. L'autre est appelé depuis les
constructeurs dérivés, et omet ces initialisations.)
A moins que le modele ait change depuis 2004 (date a laquelle j'ai ecrit
le debut de cet article), la distinction viendrait du passage de la
vtable en argument. Chaque constructeur a donc deux versions (cf ci-apres ).
Donc, en fait, le vptr des classes avec
des bases virtuelles est passé en paramètre aux constructeur de
la classe de base (c'est le deuxième paramètre de C_ctor_ et de
ABBA_ctor_, si je ne me trompe pas.)
C'est bien ca, vtt = virtual table table (parce qu'il peut y en avoir
plusieurs). Ca permet de garder un contexte local pour C_ctor_ qui
decidera des affectations necessaires. X_ctor = constructeur, X_ctor_ =
constructeur avec vtt. Pour ABBA il y aussi un ajustement d'offset dans
la vtt. Tu peux aussi regarder DCABBA_ctor_ un peu plus loin dans le
meme fichier (section ctor partiels) pour voir la difference et les
implications.C'est possible, évidemment, que ça a changé selon la version, et
que mes souvenirs se rapportent à une version plus ancienne. Ou
à un autre compilateur, ou carrément que je me souviens mal --
ça fait longtemps que je n'ai pas eu à jetter un coup d'oeil au
code généré.
Ce modele est celui decrit dans le papier de Nathan Sidwell de 2003 (la
ref est dans le html), ou du moins ce que j'en ai compris. J'ai essaye
de mettre en place ce que propose Natalie Eckel mais c'est vraiment trop
complique et n'apporte rien a l'aspect didactique du papier.
En fait ce papier est le resultat d'une discussion avec Gaby ici-meme ou
il m'avait appris que le seul moyen de respecter la norme etait de
reaffecter les vptr de this.
Comme j'avais lu que les hierarchies
complexes de classes polymorphiques pouvaient faire gonfler le code,
j'ai voulu voir les implications de ces reaffectations. Et j'ai trouve
que meme dans le cas de DCABBA qui est artificiellement compliquee
(inclue tous les cas possibles), les vtables annexes ne sont pas si
terrible que ca. Certe cette classe a 17 vtables et 1 vtt mais ca n'est
pas si gros que ca au final. Ou disont en tout cas que le modele objet
de g++ est un bon compromis d'optimisation entre espace et rapidite.
Par contre, une chose que je n'ai pas comprise, c'est pourquoi mon
implementation naive de dynamic_cast est trois fois plus rapide que
celle de g++, alors qu'il a acces a bien plus d'information (hierarchie
simple ou en diamant).