Michael DOUBEZ a écrit :
>>> [...]
>>>> En particulier avec C++, la capacite a faire un bon design est
>>>> essentielle dans un "vrai" projet.
>>> Ce n'est pas particulier avec C++ ; une bonne conception est
>>> essentielle, quelque soit le langage.
Absolument. La phase de programmation ne doit être que la mise
en oeuvre stricte de la conception. C'est pourquoi on ne
devrait pas demander à un programmeur d'être créatif mais de
maîtriser parfaitement (j'insiste sur le parfaitement) le(s)
langage(s) dans le(s)quel(s) il programme. Dans un monde de
développement idéal, le programmeur doit être le garant que la
sémantique de l'application, décrite par le dossier
fonctionnel et la conception, est respecté dans sa
transcription à l'aide d'un langage sur la machine.
>> Je considère que c'est encore plus critique sur les
>> langages statiquement typés avec peu/pas de fonctionnalités
>> dynamiques comme C et C++ et où le manque de flexibilité du
>> design se paye au prix fort.
> C'est d'autant plus vrai pour les langages dynamiques où il
> est nécessaire d'avoir une bonne couverture de test et où
> l'architecture est pressurés par la testabilité.
Dans les applications industrielles et, en particulier, temps
réel les différentes phases de tests peuvent représenter
jusqu'à 60% du total des hommes.mois du projet. Au passage,
la couverture de test ne fait pas tout, hélas. Une couverture
C1 à 100% ne garantit absolument pas l'absence de défaut...
>> Le minimum étant les interfaces à-la-Java que
>> malheureusement C++ n'a pas (mais pourrait).
Les interfaces sont les "contrats" des différents modules. Les
interfaces doivent comprendre les pré et post conditions sur
l'utilisations des différentes fonctions des interfaces.
Je ne comprends toujours pas, qu'au bout de plus de 20 ans,
C++ ne possède pas la notion d'interface.
Les classes abstraites peuvent jouer partiellement ce rôle
mais c'est en trichant avec cet idiome.
> Quel est le problème avec les classes virtuelles pures ?
> struct IConnectable
> {
> virtual connect()=0;
> virtual disconnect()=0;
> };
> Je dirai plutôt que c'est Java qui n'a pas voulu des
> héritages multiples.
Heureusement. L'héritage multiple (en C++, en particulier) est
une hérésie. Java n'a pas voulu retomber dans ces travers et
on comprend pourquoi car avec l'héritage multiple on peut
avoir plusieurs chemins d'héritage, d'où la notion de
dérivation virtuelle et des avatars que cela engendre...
Au moins, en Java, même si j'ai l'héritage d'interface, je
suis garantis que tant que je dis interface, il n'ya aucune
implémentation d'aucune méthode et quand je dis qu'une classe
"implements" une ou des interface, je dois définir (coder)
toutes les méthodes spécifiées de toutes les interfaces. C'est
donc beaucoup plus propre.
De plus, en Java, je peux déclarer qu'une classe est "final"
ce qui interdit de faire des sous-classes. Cela manque
beaucoup en C++.
Michael DOUBEZ a écrit :
>>> [...]
>>>> En particulier avec C++, la capacite a faire un bon design est
>>>> essentielle dans un "vrai" projet.
>>> Ce n'est pas particulier avec C++ ; une bonne conception est
>>> essentielle, quelque soit le langage.
Absolument. La phase de programmation ne doit être que la mise
en oeuvre stricte de la conception. C'est pourquoi on ne
devrait pas demander à un programmeur d'être créatif mais de
maîtriser parfaitement (j'insiste sur le parfaitement) le(s)
langage(s) dans le(s)quel(s) il programme. Dans un monde de
développement idéal, le programmeur doit être le garant que la
sémantique de l'application, décrite par le dossier
fonctionnel et la conception, est respecté dans sa
transcription à l'aide d'un langage sur la machine.
>> Je considère que c'est encore plus critique sur les
>> langages statiquement typés avec peu/pas de fonctionnalités
>> dynamiques comme C et C++ et où le manque de flexibilité du
>> design se paye au prix fort.
> C'est d'autant plus vrai pour les langages dynamiques où il
> est nécessaire d'avoir une bonne couverture de test et où
> l'architecture est pressurés par la testabilité.
Dans les applications industrielles et, en particulier, temps
réel les différentes phases de tests peuvent représenter
jusqu'à 60% du total des hommes.mois du projet. Au passage,
la couverture de test ne fait pas tout, hélas. Une couverture
C1 à 100% ne garantit absolument pas l'absence de défaut...
>> Le minimum étant les interfaces à-la-Java que
>> malheureusement C++ n'a pas (mais pourrait).
Les interfaces sont les "contrats" des différents modules. Les
interfaces doivent comprendre les pré et post conditions sur
l'utilisations des différentes fonctions des interfaces.
Je ne comprends toujours pas, qu'au bout de plus de 20 ans,
C++ ne possède pas la notion d'interface.
Les classes abstraites peuvent jouer partiellement ce rôle
mais c'est en trichant avec cet idiome.
> Quel est le problème avec les classes virtuelles pures ?
> struct IConnectable
> {
> virtual connect()=0;
> virtual disconnect()=0;
> };
> Je dirai plutôt que c'est Java qui n'a pas voulu des
> héritages multiples.
Heureusement. L'héritage multiple (en C++, en particulier) est
une hérésie. Java n'a pas voulu retomber dans ces travers et
on comprend pourquoi car avec l'héritage multiple on peut
avoir plusieurs chemins d'héritage, d'où la notion de
dérivation virtuelle et des avatars que cela engendre...
Au moins, en Java, même si j'ai l'héritage d'interface, je
suis garantis que tant que je dis interface, il n'ya aucune
implémentation d'aucune méthode et quand je dis qu'une classe
"implements" une ou des interface, je dois définir (coder)
toutes les méthodes spécifiées de toutes les interfaces. C'est
donc beaucoup plus propre.
De plus, en Java, je peux déclarer qu'une classe est "final"
ce qui interdit de faire des sous-classes. Cela manque
beaucoup en C++.
Michael DOUBEZ a écrit :
>>> [...]
>>>> En particulier avec C++, la capacite a faire un bon design est
>>>> essentielle dans un "vrai" projet.
>>> Ce n'est pas particulier avec C++ ; une bonne conception est
>>> essentielle, quelque soit le langage.
Absolument. La phase de programmation ne doit être que la mise
en oeuvre stricte de la conception. C'est pourquoi on ne
devrait pas demander à un programmeur d'être créatif mais de
maîtriser parfaitement (j'insiste sur le parfaitement) le(s)
langage(s) dans le(s)quel(s) il programme. Dans un monde de
développement idéal, le programmeur doit être le garant que la
sémantique de l'application, décrite par le dossier
fonctionnel et la conception, est respecté dans sa
transcription à l'aide d'un langage sur la machine.
>> Je considère que c'est encore plus critique sur les
>> langages statiquement typés avec peu/pas de fonctionnalités
>> dynamiques comme C et C++ et où le manque de flexibilité du
>> design se paye au prix fort.
> C'est d'autant plus vrai pour les langages dynamiques où il
> est nécessaire d'avoir une bonne couverture de test et où
> l'architecture est pressurés par la testabilité.
Dans les applications industrielles et, en particulier, temps
réel les différentes phases de tests peuvent représenter
jusqu'à 60% du total des hommes.mois du projet. Au passage,
la couverture de test ne fait pas tout, hélas. Une couverture
C1 à 100% ne garantit absolument pas l'absence de défaut...
>> Le minimum étant les interfaces à-la-Java que
>> malheureusement C++ n'a pas (mais pourrait).
Les interfaces sont les "contrats" des différents modules. Les
interfaces doivent comprendre les pré et post conditions sur
l'utilisations des différentes fonctions des interfaces.
Je ne comprends toujours pas, qu'au bout de plus de 20 ans,
C++ ne possède pas la notion d'interface.
Les classes abstraites peuvent jouer partiellement ce rôle
mais c'est en trichant avec cet idiome.
> Quel est le problème avec les classes virtuelles pures ?
> struct IConnectable
> {
> virtual connect()=0;
> virtual disconnect()=0;
> };
> Je dirai plutôt que c'est Java qui n'a pas voulu des
> héritages multiples.
Heureusement. L'héritage multiple (en C++, en particulier) est
une hérésie. Java n'a pas voulu retomber dans ces travers et
on comprend pourquoi car avec l'héritage multiple on peut
avoir plusieurs chemins d'héritage, d'où la notion de
dérivation virtuelle et des avatars que cela engendre...
Au moins, en Java, même si j'ai l'héritage d'interface, je
suis garantis que tant que je dis interface, il n'ya aucune
implémentation d'aucune méthode et quand je dis qu'une classe
"implements" une ou des interface, je dois définir (coder)
toutes les méthodes spécifiées de toutes les interfaces. C'est
donc beaucoup plus propre.
De plus, en Java, je peux déclarer qu'une classe est "final"
ce qui interdit de faire des sous-classes. Cela manque
beaucoup en C++.
On 30 oct, 17:59, James Kanze wrote:
> > Imagine toi assister à une réunion en Mandarin (à moins
> > que tu ne le parles?), il te sera difficile de
> > participer...
> Certes, mais pour une poste en France, je m'attendrais qu'on
> discute soit en français, soit en anglais (dans le contexte
> d'un projet international). Ou sinon qu'une connaissance de
> Mandarin soit exigée d'avance, dans quel cas, je ne me
> postulerais même pas. Mais je ne vois pas trop le rapport
> avec des compétences C++.
C'est que si il ne comprend pas le C++, une discussion
technique aura l'air d'etre du chinois pour lui et sa
participation proche de zero.
> > > Je ne sais pas. La dernière fois que j'avais à valider
> > > les compétences des candidats, je trouveais que 9 sur 10
> > > s'heurter sur des questions aussi simple que « Pourquoi
> > > est-ce qu'on déclarerait un destructeur virtuel ? ».
> > Parce que le compilateur le demande ;-)
> Justement non. Le compilateur ne le démande pas.
gcc le demande des que ta classe definit un type polymorphique.
> La réponse
> exacte comportera obligatoirement des mots « comportement
> indéfin ». Mais j'acceptais aussi des réponses à peu près
> correctes.
Entendre des mots comme "pure virtual call" ou "memory leak"
ou quelques mots sur la nature monomorphique de ::delete
serait evidement un plus ;-)
On 30 oct, 17:59, James Kanze <james.ka...@gmail.com> wrote:
> > Imagine toi assister à une réunion en Mandarin (à moins
> > que tu ne le parles?), il te sera difficile de
> > participer...
> Certes, mais pour une poste en France, je m'attendrais qu'on
> discute soit en français, soit en anglais (dans le contexte
> d'un projet international). Ou sinon qu'une connaissance de
> Mandarin soit exigée d'avance, dans quel cas, je ne me
> postulerais même pas. Mais je ne vois pas trop le rapport
> avec des compétences C++.
C'est que si il ne comprend pas le C++, une discussion
technique aura l'air d'etre du chinois pour lui et sa
participation proche de zero.
> > > Je ne sais pas. La dernière fois que j'avais à valider
> > > les compétences des candidats, je trouveais que 9 sur 10
> > > s'heurter sur des questions aussi simple que « Pourquoi
> > > est-ce qu'on déclarerait un destructeur virtuel ? ».
> > Parce que le compilateur le demande ;-)
> Justement non. Le compilateur ne le démande pas.
gcc le demande des que ta classe definit un type polymorphique.
> La réponse
> exacte comportera obligatoirement des mots « comportement
> indéfin ». Mais j'acceptais aussi des réponses à peu près
> correctes.
Entendre des mots comme "pure virtual call" ou "memory leak"
ou quelques mots sur la nature monomorphique de ::delete
serait evidement un plus ;-)
On 30 oct, 17:59, James Kanze wrote:
> > Imagine toi assister à une réunion en Mandarin (à moins
> > que tu ne le parles?), il te sera difficile de
> > participer...
> Certes, mais pour une poste en France, je m'attendrais qu'on
> discute soit en français, soit en anglais (dans le contexte
> d'un projet international). Ou sinon qu'une connaissance de
> Mandarin soit exigée d'avance, dans quel cas, je ne me
> postulerais même pas. Mais je ne vois pas trop le rapport
> avec des compétences C++.
C'est que si il ne comprend pas le C++, une discussion
technique aura l'air d'etre du chinois pour lui et sa
participation proche de zero.
> > > Je ne sais pas. La dernière fois que j'avais à valider
> > > les compétences des candidats, je trouveais que 9 sur 10
> > > s'heurter sur des questions aussi simple que « Pourquoi
> > > est-ce qu'on déclarerait un destructeur virtuel ? ».
> > Parce que le compilateur le demande ;-)
> Justement non. Le compilateur ne le démande pas.
gcc le demande des que ta classe definit un type polymorphique.
> La réponse
> exacte comportera obligatoirement des mots « comportement
> indéfin ». Mais j'acceptais aussi des réponses à peu près
> correctes.
Entendre des mots comme "pure virtual call" ou "memory leak"
ou quelques mots sur la nature monomorphique de ::delete
serait evidement un plus ;-)
Le 30/10/2008 17:55, Wykaaa a écrit :C'est bien ce qui trouble nombre de débutants en C/C++ (et même quelques
fois, des non débutants, ces espèces d'équivalences pointeurs/tableau.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
On peut même écrire : i["0123456789ABCDEF"] !Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
Cette toute dernière façon de faire est en effet assez troublante.
Le 30/10/2008 17:55, Wykaaa a écrit :
C'est bien ce qui trouble nombre de débutants en C/C++ (et même quelques
fois, des non débutants, ces espèces d'équivalences pointeurs/tableau.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
On peut même écrire : i["0123456789ABCDEF"] !
Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
Cette toute dernière façon de faire est en effet assez troublante.
Le 30/10/2008 17:55, Wykaaa a écrit :C'est bien ce qui trouble nombre de débutants en C/C++ (et même quelques
fois, des non débutants, ces espèces d'équivalences pointeurs/tableau.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
On peut même écrire : i["0123456789ABCDEF"] !Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
Cette toute dernière façon de faire est en effet assez troublante.
On peut même écrire : i["0123456789ABCDEF"] !
Cette toute dernière façon de faire est en effet assez troublante.
C'est en fait une vieille histoire car, à l'origine, K&R voulaient que
tout ce qu'on écrivait en C ait une signification.
C'est pour ça qu'on peut (pouvait ?) écrire T[i] ou i[T] !
On peut même écrire : i["0123456789ABCDEF"] !
Cette toute dernière façon de faire est en effet assez troublante.
C'est en fait une vieille histoire car, à l'origine, K&R voulaient que
tout ce qu'on écrivait en C ait une signification.
C'est pour ça qu'on peut (pouvait ?) écrire T[i] ou i[T] !
On peut même écrire : i["0123456789ABCDEF"] !
Cette toute dernière façon de faire est en effet assez troublante.
C'est en fait une vieille histoire car, à l'origine, K&R voulaient que
tout ce qu'on écrivait en C ait une signification.
C'est pour ça qu'on peut (pouvait ?) écrire T[i] ou i[T] !
On Oct 30, 4:15 pm, ld wrote:
> On 30 oct, 15:02, Michael DOUBEZ wrote:
> > ld a écrit :
> > > Le minimum étant les interfaces à-la-Java que
> > > malheureusement C++ n'a pas (mais pourrait).
> > Quel est le problème avec les classes virtuelles pures ?
> > struct IConnectable
> > {
> > virtual connect()=0;
> > virtual disconnect()=0;
> > };
> > Je dirai plutôt que c'est Java qui n'a pas voulu des
> > héritages multiples.
> Il y en a plusieurs:
> Les instances contiendront (n+1) pointeurs vers les multiples
> vtbls pour n heritage multiples, meme si ses classes
> abstraites de base n'ont pas de donnees membres (interfaces).
> Le modele objet de C++ pourrait optimiser ca, mais c'est assez
> complique a cause d'une part du "vrai" heritage multiple et
> d'autre part des contraintes dans les ctors/dtors. En tout cas
> gcc ne le fait pas. Donc si la classe implemente ± 10
> interfaces, un cas assez courant pour un design "flexible", on
> se retrouve avec 5 a 10 pointeurs dans les instances (je
> suppose quelques heritages simples dans le tas). Ces pointeurs
> necessitent des ajustements d'offsets dans l'instance a chaque
> fois que l'on utilise l'instance a travers une de ses
> interfaces (ce qui est le but pour la flexibilite), et ce qui
> demande de generer du code et des calculs en plus.
Et alors ? Mieux vaut que ce soit le compilateur qui s'en
occupe, et pas toi, non ?
Et si tu as une classe qui implémente
plusieurs interfaces, quel est l'alternatif ?
Sans l'héritage
multiple, tu es bien obligé d'y introduire autant de
passerelles. Donc, des objets supplémentaires, avec leur vptr,
etc., un niveau d'indirection en plus, ET qui sont écrit par
toi, donc beaucoup plus de travail (et plus de possibilités
d'erreur).
> De plus il faut utiliser au maximum les retours contravariant
> pour garder un maximum de visibilite statique,
L'utilisation d'un retour contravariant est en général une
indication d'une mauvaise conception, ou peut-être plutôt une
paresse dans la définition de l'interface. À éviter.
> Enfin, en theorie, il faudrait deriver virtuellement *par
> defaut* pour un design flexible sans "bombe a retardement",
Dans le cas où une interface étend une autre, absolument. C'est
une règle de programmation à ne pas effreindre. Il serait bien
préférable que l'héritage virtuel soit le défaut, parce qu'il
marche dans prèsque tous les cas. Mais il n'est pas essentiel
quand on hérite afin d'implémenter, plutôt que pour étendre
l'interface, parce qu'à ce moment là, au moins d'utiliser un
idiome comme le modèle de template (« template method
pattern » en anglais -- rien à voir avec les templates C++),
on sait qu'on a affaire à la classe la plus dérivée, et donc que
l'héritage virtuel n'est pas nécessaire.
> mais cela rajoute encore des pointeurs des des ajustements
> d'offset. Au final tous ces calculs intermediaires
> ralentissent la resolution des appels virtuels de facon non
> negligeable. Mes mesures sur gcc 4.2 montrent que par rapport
> a un appel virtuel avec heritage simple, l'heritage multiple
> de 4 interfaces ralentit d'un facteur ±2 l'appel, et
> l'heritage virtuel d'un facteur ±4. Soit ±8 au total si on
> mixe les deux, ce qui serait l'approche correcte. Je ne pense
> pas que les autres compilateur font mieux et je trouve deja
> pas mal compte tenu de tout ce que le compilateur doit
> generer.
Et alors ? Est-ce que tu connais des applications où ça fait
une différence ?
Je me suis servi parfois des graphes
d'héritage à peine croyable (à cause des contraints techniques
liés à la façon qu'on générait le code), et ça n'a jamais eu un
impact mesurable sur les performances de l'application.
> La faute au "vrai" heritage multiple.
Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
Et comment est-ce qu'il est implémenté ?
> Maintenant de mon point de vue, qui n'engage que moi, je
> trouverais beaucoup plus utile une implementation tres
> efficace des interfaces a- la-Java en C++ (space & time) a la
> place de l'heritage multiple rarement utile et facilement
> simulable quand on a vraiment besoin.
Quand tu utilises l'héritage multiple en Java, ça fonctionne
plus ou moins comme l'héritage multiple en C++. De point de vue
pratique, quand tu « extend » une interface, c'est exactement
comme si tu héritais virtuellement de la classe en C++.
On Oct 30, 4:15 pm, ld <Laurent.Den...@gmail.com> wrote:
> On 30 oct, 15:02, Michael DOUBEZ <michael.dou...@free.fr> wrote:
> > ld a écrit :
> > > Le minimum étant les interfaces à-la-Java que
> > > malheureusement C++ n'a pas (mais pourrait).
> > Quel est le problème avec les classes virtuelles pures ?
> > struct IConnectable
> > {
> > virtual connect()=0;
> > virtual disconnect()=0;
> > };
> > Je dirai plutôt que c'est Java qui n'a pas voulu des
> > héritages multiples.
> Il y en a plusieurs:
> Les instances contiendront (n+1) pointeurs vers les multiples
> vtbls pour n heritage multiples, meme si ses classes
> abstraites de base n'ont pas de donnees membres (interfaces).
> Le modele objet de C++ pourrait optimiser ca, mais c'est assez
> complique a cause d'une part du "vrai" heritage multiple et
> d'autre part des contraintes dans les ctors/dtors. En tout cas
> gcc ne le fait pas. Donc si la classe implemente ± 10
> interfaces, un cas assez courant pour un design "flexible", on
> se retrouve avec 5 a 10 pointeurs dans les instances (je
> suppose quelques heritages simples dans le tas). Ces pointeurs
> necessitent des ajustements d'offsets dans l'instance a chaque
> fois que l'on utilise l'instance a travers une de ses
> interfaces (ce qui est le but pour la flexibilite), et ce qui
> demande de generer du code et des calculs en plus.
Et alors ? Mieux vaut que ce soit le compilateur qui s'en
occupe, et pas toi, non ?
Et si tu as une classe qui implémente
plusieurs interfaces, quel est l'alternatif ?
Sans l'héritage
multiple, tu es bien obligé d'y introduire autant de
passerelles. Donc, des objets supplémentaires, avec leur vptr,
etc., un niveau d'indirection en plus, ET qui sont écrit par
toi, donc beaucoup plus de travail (et plus de possibilités
d'erreur).
> De plus il faut utiliser au maximum les retours contravariant
> pour garder un maximum de visibilite statique,
L'utilisation d'un retour contravariant est en général une
indication d'une mauvaise conception, ou peut-être plutôt une
paresse dans la définition de l'interface. À éviter.
> Enfin, en theorie, il faudrait deriver virtuellement *par
> defaut* pour un design flexible sans "bombe a retardement",
Dans le cas où une interface étend une autre, absolument. C'est
une règle de programmation à ne pas effreindre. Il serait bien
préférable que l'héritage virtuel soit le défaut, parce qu'il
marche dans prèsque tous les cas. Mais il n'est pas essentiel
quand on hérite afin d'implémenter, plutôt que pour étendre
l'interface, parce qu'à ce moment là, au moins d'utiliser un
idiome comme le modèle de template (« template method
pattern » en anglais -- rien à voir avec les templates C++),
on sait qu'on a affaire à la classe la plus dérivée, et donc que
l'héritage virtuel n'est pas nécessaire.
> mais cela rajoute encore des pointeurs des des ajustements
> d'offset. Au final tous ces calculs intermediaires
> ralentissent la resolution des appels virtuels de facon non
> negligeable. Mes mesures sur gcc 4.2 montrent que par rapport
> a un appel virtuel avec heritage simple, l'heritage multiple
> de 4 interfaces ralentit d'un facteur ±2 l'appel, et
> l'heritage virtuel d'un facteur ±4. Soit ±8 au total si on
> mixe les deux, ce qui serait l'approche correcte. Je ne pense
> pas que les autres compilateur font mieux et je trouve deja
> pas mal compte tenu de tout ce que le compilateur doit
> generer.
Et alors ? Est-ce que tu connais des applications où ça fait
une différence ?
Je me suis servi parfois des graphes
d'héritage à peine croyable (à cause des contraints techniques
liés à la façon qu'on générait le code), et ça n'a jamais eu un
impact mesurable sur les performances de l'application.
> La faute au "vrai" heritage multiple.
Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
Et comment est-ce qu'il est implémenté ?
> Maintenant de mon point de vue, qui n'engage que moi, je
> trouverais beaucoup plus utile une implementation tres
> efficace des interfaces a- la-Java en C++ (space & time) a la
> place de l'heritage multiple rarement utile et facilement
> simulable quand on a vraiment besoin.
Quand tu utilises l'héritage multiple en Java, ça fonctionne
plus ou moins comme l'héritage multiple en C++. De point de vue
pratique, quand tu « extend » une interface, c'est exactement
comme si tu héritais virtuellement de la classe en C++.
On Oct 30, 4:15 pm, ld wrote:
> On 30 oct, 15:02, Michael DOUBEZ wrote:
> > ld a écrit :
> > > Le minimum étant les interfaces à-la-Java que
> > > malheureusement C++ n'a pas (mais pourrait).
> > Quel est le problème avec les classes virtuelles pures ?
> > struct IConnectable
> > {
> > virtual connect()=0;
> > virtual disconnect()=0;
> > };
> > Je dirai plutôt que c'est Java qui n'a pas voulu des
> > héritages multiples.
> Il y en a plusieurs:
> Les instances contiendront (n+1) pointeurs vers les multiples
> vtbls pour n heritage multiples, meme si ses classes
> abstraites de base n'ont pas de donnees membres (interfaces).
> Le modele objet de C++ pourrait optimiser ca, mais c'est assez
> complique a cause d'une part du "vrai" heritage multiple et
> d'autre part des contraintes dans les ctors/dtors. En tout cas
> gcc ne le fait pas. Donc si la classe implemente ± 10
> interfaces, un cas assez courant pour un design "flexible", on
> se retrouve avec 5 a 10 pointeurs dans les instances (je
> suppose quelques heritages simples dans le tas). Ces pointeurs
> necessitent des ajustements d'offsets dans l'instance a chaque
> fois que l'on utilise l'instance a travers une de ses
> interfaces (ce qui est le but pour la flexibilite), et ce qui
> demande de generer du code et des calculs en plus.
Et alors ? Mieux vaut que ce soit le compilateur qui s'en
occupe, et pas toi, non ?
Et si tu as une classe qui implémente
plusieurs interfaces, quel est l'alternatif ?
Sans l'héritage
multiple, tu es bien obligé d'y introduire autant de
passerelles. Donc, des objets supplémentaires, avec leur vptr,
etc., un niveau d'indirection en plus, ET qui sont écrit par
toi, donc beaucoup plus de travail (et plus de possibilités
d'erreur).
> De plus il faut utiliser au maximum les retours contravariant
> pour garder un maximum de visibilite statique,
L'utilisation d'un retour contravariant est en général une
indication d'une mauvaise conception, ou peut-être plutôt une
paresse dans la définition de l'interface. À éviter.
> Enfin, en theorie, il faudrait deriver virtuellement *par
> defaut* pour un design flexible sans "bombe a retardement",
Dans le cas où une interface étend une autre, absolument. C'est
une règle de programmation à ne pas effreindre. Il serait bien
préférable que l'héritage virtuel soit le défaut, parce qu'il
marche dans prèsque tous les cas. Mais il n'est pas essentiel
quand on hérite afin d'implémenter, plutôt que pour étendre
l'interface, parce qu'à ce moment là, au moins d'utiliser un
idiome comme le modèle de template (« template method
pattern » en anglais -- rien à voir avec les templates C++),
on sait qu'on a affaire à la classe la plus dérivée, et donc que
l'héritage virtuel n'est pas nécessaire.
> mais cela rajoute encore des pointeurs des des ajustements
> d'offset. Au final tous ces calculs intermediaires
> ralentissent la resolution des appels virtuels de facon non
> negligeable. Mes mesures sur gcc 4.2 montrent que par rapport
> a un appel virtuel avec heritage simple, l'heritage multiple
> de 4 interfaces ralentit d'un facteur ±2 l'appel, et
> l'heritage virtuel d'un facteur ±4. Soit ±8 au total si on
> mixe les deux, ce qui serait l'approche correcte. Je ne pense
> pas que les autres compilateur font mieux et je trouve deja
> pas mal compte tenu de tout ce que le compilateur doit
> generer.
Et alors ? Est-ce que tu connais des applications où ça fait
une différence ?
Je me suis servi parfois des graphes
d'héritage à peine croyable (à cause des contraints techniques
liés à la façon qu'on générait le code), et ça n'a jamais eu un
impact mesurable sur les performances de l'application.
> La faute au "vrai" heritage multiple.
Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
Et comment est-ce qu'il est implémenté ?
> Maintenant de mon point de vue, qui n'engage que moi, je
> trouverais beaucoup plus utile une implementation tres
> efficace des interfaces a- la-Java en C++ (space & time) a la
> place de l'heritage multiple rarement utile et facilement
> simulable quand on a vraiment besoin.
Quand tu utilises l'héritage multiple en Java, ça fonctionne
plus ou moins comme l'héritage multiple en C++. De point de vue
pratique, quand tu « extend » une interface, c'est exactement
comme si tu héritais virtuellement de la classe en C++.
int tab[25] = { ... }; // un tableau
int* ptr = &tab[0]; // un pointeur sur le premier élément du tableau++ptr; // élément suivant
Mais où est le pointeur sur tableau là-dedans. Plutôt :
int (*ptr)[ 25 ] = &tab ;
, il me semble. C'est au moins ce que je m'entends par
« pointeur sur tableau ».
int tab[25] = { ... }; // un tableau
int* ptr = &tab[0]; // un pointeur sur le premier élément du tableau
++ptr; // élément suivant
Mais où est le pointeur sur tableau là-dedans. Plutôt :
int (*ptr)[ 25 ] = &tab ;
, il me semble. C'est au moins ce que je m'entends par
« pointeur sur tableau ».
int tab[25] = { ... }; // un tableau
int* ptr = &tab[0]; // un pointeur sur le premier élément du tableau++ptr; // élément suivant
Mais où est le pointeur sur tableau là-dedans. Plutôt :
int (*ptr)[ 25 ] = &tab ;
, il me semble. C'est au moins ce que je m'entends par
« pointeur sur tableau ».
On Oct 30, 3:02 pm, Michael DOUBEZ wrote:ld a écrit :On 30 oct, 10:37, James Kanze wrote:On Oct 30, 8:41 am, ld wrote:On 28 oct, 19:13, David Fleury wrote:
[...]En particulier avec C++, la capacite a faire un bon design
est essentielle dans un "vrai" projet.
Ce n'est pas particulier avec C++ ; une bonne conception
est essentielle, quelque soit le langage.Je considère que c'est encore plus critique sur les langages
statiquement typés avec peu/pas de fonctionnalités
dynamiques comme C et C++ et où le manque de flexibilité du
design se paye au prix fort.C'est d'autant plus vrai pour les langages dynamiques où il
est nécessaire d'avoir une bonne couverture de test et où
l'architecture est pressurés par la testabilité.Le minimum étant les interfaces à-la-Java que
malheureusement C++ n'a pas (mais pourrait).Quel est le problème avec les classes virtuelles pures ?struct IConnectable
{
virtual connect()=0;
virtual disconnect()=0;
};
Le problème, c'est que tu ne peux pas vérifier le contrat. Dans
les cas où il n'y a pas de contrat (les inversions d'appel, par
exemple), c'est bien, mais dans les autres, en général, l'idiome
consacré est plutôt :
class Connectable
{
public
void connect()
{
// vérifier les préconditions...
doConnect() ;
// vérifier les postconditions...
}
private:
virtual void doConnect() = 0 ;
} ;
Une interface qui impose un contrat doit avoir du code, qu'il
soit généré implicitement (comme en Eiffel) ou explicitement
(comme en C++).
Je dirai plutôt que c'est Java qui n'a pas voulu des héritages
multiples.
Dans le cas des interfaces, si. Ce qu'il n'a pas voulu, c'est
l'enforcement des contrats. Donc, de vraies interfaces typées.
On Oct 30, 3:02 pm, Michael DOUBEZ <michael.dou...@free.fr> wrote:
ld a écrit :
On 30 oct, 10:37, James Kanze <james.ka...@gmail.com> wrote:
On Oct 30, 8:41 am, ld <Laurent.Den...@gmail.com> wrote:
On 28 oct, 19:13, David Fleury <dfleu...@libertysurf.fr> wrote:
[...]
En particulier avec C++, la capacite a faire un bon design
est essentielle dans un "vrai" projet.
Ce n'est pas particulier avec C++ ; une bonne conception
est essentielle, quelque soit le langage.
Je considère que c'est encore plus critique sur les langages
statiquement typés avec peu/pas de fonctionnalités
dynamiques comme C et C++ et où le manque de flexibilité du
design se paye au prix fort.
C'est d'autant plus vrai pour les langages dynamiques où il
est nécessaire d'avoir une bonne couverture de test et où
l'architecture est pressurés par la testabilité.
Le minimum étant les interfaces à-la-Java que
malheureusement C++ n'a pas (mais pourrait).
Quel est le problème avec les classes virtuelles pures ?
struct IConnectable
{
virtual connect()=0;
virtual disconnect()=0;
};
Le problème, c'est que tu ne peux pas vérifier le contrat. Dans
les cas où il n'y a pas de contrat (les inversions d'appel, par
exemple), c'est bien, mais dans les autres, en général, l'idiome
consacré est plutôt :
class Connectable
{
public
void connect()
{
// vérifier les préconditions...
doConnect() ;
// vérifier les postconditions...
}
private:
virtual void doConnect() = 0 ;
} ;
Une interface qui impose un contrat doit avoir du code, qu'il
soit généré implicitement (comme en Eiffel) ou explicitement
(comme en C++).
Je dirai plutôt que c'est Java qui n'a pas voulu des héritages
multiples.
Dans le cas des interfaces, si. Ce qu'il n'a pas voulu, c'est
l'enforcement des contrats. Donc, de vraies interfaces typées.
On Oct 30, 3:02 pm, Michael DOUBEZ wrote:ld a écrit :On 30 oct, 10:37, James Kanze wrote:On Oct 30, 8:41 am, ld wrote:On 28 oct, 19:13, David Fleury wrote:
[...]En particulier avec C++, la capacite a faire un bon design
est essentielle dans un "vrai" projet.
Ce n'est pas particulier avec C++ ; une bonne conception
est essentielle, quelque soit le langage.Je considère que c'est encore plus critique sur les langages
statiquement typés avec peu/pas de fonctionnalités
dynamiques comme C et C++ et où le manque de flexibilité du
design se paye au prix fort.C'est d'autant plus vrai pour les langages dynamiques où il
est nécessaire d'avoir une bonne couverture de test et où
l'architecture est pressurés par la testabilité.Le minimum étant les interfaces à-la-Java que
malheureusement C++ n'a pas (mais pourrait).Quel est le problème avec les classes virtuelles pures ?struct IConnectable
{
virtual connect()=0;
virtual disconnect()=0;
};
Le problème, c'est que tu ne peux pas vérifier le contrat. Dans
les cas où il n'y a pas de contrat (les inversions d'appel, par
exemple), c'est bien, mais dans les autres, en général, l'idiome
consacré est plutôt :
class Connectable
{
public
void connect()
{
// vérifier les préconditions...
doConnect() ;
// vérifier les postconditions...
}
private:
virtual void doConnect() = 0 ;
} ;
Une interface qui impose un contrat doit avoir du code, qu'il
soit généré implicitement (comme en Eiffel) ou explicitement
(comme en C++).
Je dirai plutôt que c'est Java qui n'a pas voulu des héritages
multiples.
Dans le cas des interfaces, si. Ce qu'il n'a pas voulu, c'est
l'enforcement des contrats. Donc, de vraies interfaces typées.
Olivier Miakinen a écrit :> Le 30/10/2008 17:55, Wykaaa a écrit :
>> C'est bien ce qui trouble nombre de débutants en C/C++ (et
>> même quelques fois, des non débutants, ces espèces
>> d'équivalences pointeurs/tableau.
>> D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
> On peut même écrire : i["0123456789ABCDEF"] !
>> Je peux te garantir que ceci trouble même certains
>> programmeurs C très expérimentés.
> Cette toute dernière façon de faire est en effet assez
> troublante.
C'est en fait une vieille histoire car, à l'origine, K&R
voulaient que tout ce qu'on écrivait en C ait une
signification. C'est pour ça qu'on peut (pouvait ?) écrire
T[i] ou i[T] !
Olivier Miakinen a écrit :> Le 30/10/2008 17:55, Wykaaa a écrit :
>> C'est bien ce qui trouble nombre de débutants en C/C++ (et
>> même quelques fois, des non débutants, ces espèces
>> d'équivalences pointeurs/tableau.
>> D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
> On peut même écrire : i["0123456789ABCDEF"] !
>> Je peux te garantir que ceci trouble même certains
>> programmeurs C très expérimentés.
> Cette toute dernière façon de faire est en effet assez
> troublante.
C'est en fait une vieille histoire car, à l'origine, K&R
voulaient que tout ce qu'on écrivait en C ait une
signification. C'est pour ça qu'on peut (pouvait ?) écrire
T[i] ou i[T] !
Olivier Miakinen a écrit :> Le 30/10/2008 17:55, Wykaaa a écrit :
>> C'est bien ce qui trouble nombre de débutants en C/C++ (et
>> même quelques fois, des non débutants, ces espèces
>> d'équivalences pointeurs/tableau.
>> D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
> On peut même écrire : i["0123456789ABCDEF"] !
>> Je peux te garantir que ceci trouble même certains
>> programmeurs C très expérimentés.
> Cette toute dernière façon de faire est en effet assez
> troublante.
C'est en fait une vieille histoire car, à l'origine, K&R
voulaient que tout ce qu'on écrivait en C ait une
signification. C'est pour ça qu'on peut (pouvait ?) écrire
T[i] ou i[T] !
On 30 oct, 18:51, James Kanze wrote:
> On Oct 30, 4:15 pm, ld wrote:
> > On 30 oct, 15:02, Michael DOUBEZ wrote:
> Et si tu as une classe qui implémente plusieurs interfaces,
> quel est l'alternatif ?
L'heritage multiple d'interface comme en Java. Aucun surcout,
simple et efficace.
Mais pas de contrat possible ou de template method pattern
sans beaucoup devoir ecrire beaucoup code.
> Sans l'héritage multiple, tu es bien obligé d'y introduire
> autant de passerelles. Donc, des objets supplémentaires,
> avec leur vptr, etc., un niveau d'indirection en plus, ET
> qui sont écrit par toi, donc beaucoup plus de travail (et
> plus de possibilités d'erreur).
Il faut l'heritage multiple, mais d'interface seulement.
Note qu'il n'y a pas d'objection pratique pour que Java puisse
avoir des implementations par defaut dans ces interfaces, ce
qui simplifierait les template method pattern et les contrats.
> > De plus il faut utiliser au maximum les retours
> > contravariant pour garder un maximum de visibilite
> > statique,
> L'utilisation d'un retour contravariant est en général une
> indication d'une mauvaise conception, ou peut-être plutôt
> une paresse dans la définition de l'interface. À éviter.
Les retours contravariant ne sont pas dans les interfaces, par
definition...
C'est dans les implementations qu'on les utilise (overriding)
pour conserver un maximum de visibilite statique quand on
utilise la classe sans passer par ses interfaces.
> > mais cela rajoute encore des pointeurs des des ajustements
> > d'offset. Au final tous ces calculs intermediaires
> > ralentissent la resolution des appels virtuels de facon
> > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > rapport a un appel virtuel avec heritage simple,
> > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > au total si on mixe les deux, ce qui serait l'approche
> > correcte. Je ne pense pas que les autres compilateur font
> > mieux et je trouve deja pas mal compte tenu de tout ce que
> > le compilateur doit generer.
> Et alors ? Est-ce que tu connais des applications où ça fait
> une différence ?
oui, presque toutes celles sur lesquelles je travaille. C'est
tout simplement la difference entre une application qui donne
un resultat et rien du tout. Le cas le plus fameux ici, c'est
le simulateur d'accelerateur. La version (ancienne) Fortran
est environ 10 fois plus rapide que la version (recente)
ecrite en C++, du coup cette derniere n'est jamais utilisee
(les temps de calculs etant deja en heures voir en jours pour
la version Fortran). L'autre probleme vient de la complexite
du code C++ que personne ne veut reprendre.
> Je me suis servi parfois des graphes d'héritage à peine
> croyable (à cause des contraints techniques liés à la façon
> qu'on générait le code), et ça n'a jamais eu un impact
> mesurable sur les performances de l'application.
Un facteur 10 n'est pas negligeable lorsque les calculs
prennent des heures, voir des jours pour les versions non
polymorphique. Mais c'est vrai que dans la plupart des
application, on ne voit probablement pas grand chose.
> > La faute au "vrai" heritage multiple.
> Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
> Et comment est-ce qu'il est implémenté ?
Je te laisse te documenter.
Un point de depart possible, c'est le papier sur le modele
objet de C++ que j'avais commence en 2005 ou le papier de
Nathan Sidwell qui si je me souviens bien decrivait celui de
g++.
le D&E est pas mal aussi si on lit entre les lignes.
> > Maintenant de mon point de vue, qui n'engage que moi, je
> > trouverais beaucoup plus utile une implementation tres
> > efficace des interfaces a- la-Java en C++ (space & time) a la
> > place de l'heritage multiple rarement utile et facilement
> > simulable quand on a vraiment besoin.
> Quand tu utilises l'héritage multiple en Java, ça fonctionne
> plus ou moins comme l'héritage multiple en C++. De point de
> vue pratique, quand tu « extend » une interface, c'est
> exactement comme si tu héritais virtuellement de la classe
> en C++.
Techniquement, pas du tout. L'heritage multiple de Java peut
se faire en C assez facilement, et sans besoin de code
supplementaire et avec une excellente performance. Celui de
C++, c'est exclus.
On 30 oct, 18:51, James Kanze <james.ka...@gmail.com> wrote:
> On Oct 30, 4:15 pm, ld <Laurent.Den...@gmail.com> wrote:
> > On 30 oct, 15:02, Michael DOUBEZ <michael.dou...@free.fr> wrote:
> Et si tu as une classe qui implémente plusieurs interfaces,
> quel est l'alternatif ?
L'heritage multiple d'interface comme en Java. Aucun surcout,
simple et efficace.
Mais pas de contrat possible ou de template method pattern
sans beaucoup devoir ecrire beaucoup code.
> Sans l'héritage multiple, tu es bien obligé d'y introduire
> autant de passerelles. Donc, des objets supplémentaires,
> avec leur vptr, etc., un niveau d'indirection en plus, ET
> qui sont écrit par toi, donc beaucoup plus de travail (et
> plus de possibilités d'erreur).
Il faut l'heritage multiple, mais d'interface seulement.
Note qu'il n'y a pas d'objection pratique pour que Java puisse
avoir des implementations par defaut dans ces interfaces, ce
qui simplifierait les template method pattern et les contrats.
> > De plus il faut utiliser au maximum les retours
> > contravariant pour garder un maximum de visibilite
> > statique,
> L'utilisation d'un retour contravariant est en général une
> indication d'une mauvaise conception, ou peut-être plutôt
> une paresse dans la définition de l'interface. À éviter.
Les retours contravariant ne sont pas dans les interfaces, par
definition...
C'est dans les implementations qu'on les utilise (overriding)
pour conserver un maximum de visibilite statique quand on
utilise la classe sans passer par ses interfaces.
> > mais cela rajoute encore des pointeurs des des ajustements
> > d'offset. Au final tous ces calculs intermediaires
> > ralentissent la resolution des appels virtuels de facon
> > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > rapport a un appel virtuel avec heritage simple,
> > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > au total si on mixe les deux, ce qui serait l'approche
> > correcte. Je ne pense pas que les autres compilateur font
> > mieux et je trouve deja pas mal compte tenu de tout ce que
> > le compilateur doit generer.
> Et alors ? Est-ce que tu connais des applications où ça fait
> une différence ?
oui, presque toutes celles sur lesquelles je travaille. C'est
tout simplement la difference entre une application qui donne
un resultat et rien du tout. Le cas le plus fameux ici, c'est
le simulateur d'accelerateur. La version (ancienne) Fortran
est environ 10 fois plus rapide que la version (recente)
ecrite en C++, du coup cette derniere n'est jamais utilisee
(les temps de calculs etant deja en heures voir en jours pour
la version Fortran). L'autre probleme vient de la complexite
du code C++ que personne ne veut reprendre.
> Je me suis servi parfois des graphes d'héritage à peine
> croyable (à cause des contraints techniques liés à la façon
> qu'on générait le code), et ça n'a jamais eu un impact
> mesurable sur les performances de l'application.
Un facteur 10 n'est pas negligeable lorsque les calculs
prennent des heures, voir des jours pour les versions non
polymorphique. Mais c'est vrai que dans la plupart des
application, on ne voit probablement pas grand chose.
> > La faute au "vrai" heritage multiple.
> Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
> Et comment est-ce qu'il est implémenté ?
Je te laisse te documenter.
Un point de depart possible, c'est le papier sur le modele
objet de C++ que j'avais commence en 2005 ou le papier de
Nathan Sidwell qui si je me souviens bien decrivait celui de
g++.
le D&E est pas mal aussi si on lit entre les lignes.
> > Maintenant de mon point de vue, qui n'engage que moi, je
> > trouverais beaucoup plus utile une implementation tres
> > efficace des interfaces a- la-Java en C++ (space & time) a la
> > place de l'heritage multiple rarement utile et facilement
> > simulable quand on a vraiment besoin.
> Quand tu utilises l'héritage multiple en Java, ça fonctionne
> plus ou moins comme l'héritage multiple en C++. De point de
> vue pratique, quand tu « extend » une interface, c'est
> exactement comme si tu héritais virtuellement de la classe
> en C++.
Techniquement, pas du tout. L'heritage multiple de Java peut
se faire en C assez facilement, et sans besoin de code
supplementaire et avec une excellente performance. Celui de
C++, c'est exclus.
On 30 oct, 18:51, James Kanze wrote:
> On Oct 30, 4:15 pm, ld wrote:
> > On 30 oct, 15:02, Michael DOUBEZ wrote:
> Et si tu as une classe qui implémente plusieurs interfaces,
> quel est l'alternatif ?
L'heritage multiple d'interface comme en Java. Aucun surcout,
simple et efficace.
Mais pas de contrat possible ou de template method pattern
sans beaucoup devoir ecrire beaucoup code.
> Sans l'héritage multiple, tu es bien obligé d'y introduire
> autant de passerelles. Donc, des objets supplémentaires,
> avec leur vptr, etc., un niveau d'indirection en plus, ET
> qui sont écrit par toi, donc beaucoup plus de travail (et
> plus de possibilités d'erreur).
Il faut l'heritage multiple, mais d'interface seulement.
Note qu'il n'y a pas d'objection pratique pour que Java puisse
avoir des implementations par defaut dans ces interfaces, ce
qui simplifierait les template method pattern et les contrats.
> > De plus il faut utiliser au maximum les retours
> > contravariant pour garder un maximum de visibilite
> > statique,
> L'utilisation d'un retour contravariant est en général une
> indication d'une mauvaise conception, ou peut-être plutôt
> une paresse dans la définition de l'interface. À éviter.
Les retours contravariant ne sont pas dans les interfaces, par
definition...
C'est dans les implementations qu'on les utilise (overriding)
pour conserver un maximum de visibilite statique quand on
utilise la classe sans passer par ses interfaces.
> > mais cela rajoute encore des pointeurs des des ajustements
> > d'offset. Au final tous ces calculs intermediaires
> > ralentissent la resolution des appels virtuels de facon
> > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > rapport a un appel virtuel avec heritage simple,
> > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > au total si on mixe les deux, ce qui serait l'approche
> > correcte. Je ne pense pas que les autres compilateur font
> > mieux et je trouve deja pas mal compte tenu de tout ce que
> > le compilateur doit generer.
> Et alors ? Est-ce que tu connais des applications où ça fait
> une différence ?
oui, presque toutes celles sur lesquelles je travaille. C'est
tout simplement la difference entre une application qui donne
un resultat et rien du tout. Le cas le plus fameux ici, c'est
le simulateur d'accelerateur. La version (ancienne) Fortran
est environ 10 fois plus rapide que la version (recente)
ecrite en C++, du coup cette derniere n'est jamais utilisee
(les temps de calculs etant deja en heures voir en jours pour
la version Fortran). L'autre probleme vient de la complexite
du code C++ que personne ne veut reprendre.
> Je me suis servi parfois des graphes d'héritage à peine
> croyable (à cause des contraints techniques liés à la façon
> qu'on générait le code), et ça n'a jamais eu un impact
> mesurable sur les performances de l'application.
Un facteur 10 n'est pas negligeable lorsque les calculs
prennent des heures, voir des jours pour les versions non
polymorphique. Mais c'est vrai que dans la plupart des
application, on ne voit probablement pas grand chose.
> > La faute au "vrai" heritage multiple.
> Et qu'est-ce que tu appelles un « vrai » héritage multiple ?
> Et comment est-ce qu'il est implémenté ?
Je te laisse te documenter.
Un point de depart possible, c'est le papier sur le modele
objet de C++ que j'avais commence en 2005 ou le papier de
Nathan Sidwell qui si je me souviens bien decrivait celui de
g++.
le D&E est pas mal aussi si on lit entre les lignes.
> > Maintenant de mon point de vue, qui n'engage que moi, je
> > trouverais beaucoup plus utile une implementation tres
> > efficace des interfaces a- la-Java en C++ (space & time) a la
> > place de l'heritage multiple rarement utile et facilement
> > simulable quand on a vraiment besoin.
> Quand tu utilises l'héritage multiple en Java, ça fonctionne
> plus ou moins comme l'héritage multiple en C++. De point de
> vue pratique, quand tu « extend » une interface, c'est
> exactement comme si tu héritais virtuellement de la classe
> en C++.
Techniquement, pas du tout. L'heritage multiple de Java peut
se faire en C assez facilement, et sans besoin de code
supplementaire et avec une excellente performance. Celui de
C++, c'est exclus.
Dans la pratique, j'admets qu'il peut y avoir des cas assez
rares où y insister serait un peu exagérer. Mais quand même :
en dehors des cas d'inversion d'appel,
où tu n'as pas de
contrat, l'idiome classique serait :
class Base // ou Interface, comme tu veux...
{
public:
Base* function()
{
// ...
return doFunction() ;
}
private:
virtual Base* doFunction() = 0 ;
} ;
class Derived : public Base
{ // ou class Implementation, si tu préfère.
public:
Derived* function() // Si on veut ; dans mon exp érience,
{ // c'est ra rement nécessaire.
// ...
}
private:
virtual Base* doFunction()
{
return function() ;
}
} ;
En fait, j'ai utilisé ce modèle beaucoup plus souvent pour des
raisons d'optimisation que pour des raisons de typage,
par
exemple, quand la fonction publique est operator[] sur une
classe qui implémente quelque chose comme vector (où le coût
d'un appel virtuel, et surtout l'impossibilité de générer la
fonction inline que cela implique, pourrait avoir des
consequences réeles sur les performances des applications).
Sinon, typiquement, la fonction publique dans Base s'appellera
quelque chose comme getBase, et celle dans Derived getDerived.
> > > mais cela rajoute encore des pointeurs des des ajustements
> > > d'offset. Au final tous ces calculs intermediaires
> > > ralentissent la resolution des appels virtuels de facon
> > > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > > rapport a un appel virtuel avec heritage simple,
> > > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > > au total si on mixe les deux, ce qui serait l'approche
> > > correcte. Je ne pense pas que les autres compilateur font
> > > mieux et je trouve deja pas mal compte tenu de tout ce que
> > > le compilateur doit generer.
> > Et alors ? Est-ce que tu connais des applications où ça fait
> > une différence ?
> oui, presque toutes celles sur lesquelles je travaille. C'est
> tout simplement la difference entre une application qui donne
> un resultat et rien du tout. Le cas le plus fameux ici, c'est
> le simulateur d'accelerateur. La version (ancienne) Fortran
> est environ 10 fois plus rapide que la version (recente)
> ecrite en C++, du coup cette derniere n'est jamais utilisee
> (les temps de calculs etant deja en heures voir en jours pour
> la version Fortran). L'autre probleme vient de la complexite
> du code C++ que personne ne veut reprendre.
Mais qu'est-ce que tu fais dans tes fonctions ? Quasiment rien,
alors.
Et alors, est-ce que le problème n'est pas plutôt du à la
virtualité en soi, qui empêche (en général) la génération
inline, et non à l'héritage multiple. Aussi, n'oublie pas que
même aujourd'hui, les compilateurs Fortran optimisent bien mieux
que ceux de C++. (Si tu es concerné par le calcul numérique, et
des performances, je conseille très fortement la lecture de
Barton et Nackman. Qui utilise intensement l'héritage multiple,
sans que ça ait un impact négatif sur les performancese.)
> > Je me suis servi parfois des graphes d'héritage à peine
> > croyable (à cause des contraints techniques liés à la façon
> > qu'on générait le code), et ça n'a jamais eu un impact
> > mesurable sur les performances de l'application.
> Un facteur 10 n'est pas negligeable lorsque les calculs
> prennent des heures, voir des jours pour les versions non
> polymorphique. Mais c'est vrai que dans la plupart des
> application, on ne voit probablement pas grand chose.
Le facteur 10, c'est dans l'appel de la fonction, tout au plus.
Comment est-ce que je peux me documenter sur ce que toi, tu
entends par un « vrai » héritage multiple ?
en quoi est-ce que l'héritage multiple de C++ (ou
de Java) n'est pas vrai ?
> Un point de depart possible, c'est le papier sur le modele
> objet de C++ que j'avais commence en 2005 ou le papier de
> Nathan Sidwell qui si je me souviens bien decrivait celui de
> g++.
Papiers qui se trouvent où.
> le D&E est pas mal aussi si on lit entre les lignes.
Je l'ai lu il y a un certain temps. J'ai aussi discuté avec
Stroustrup une ou deux fois, et avec les auteurs du compilateur
EDG et les auteurs de Sun CC. Autant que j'ai pu comprendre,
tous, ils considèrent l'héritage multiple un C++ un « vrai »
héritage multiple.
> L'heritage multiple de Java peut
> se faire en C assez facilement, et sans besoin de code
> supplementaire et avec une excellente performance. Celui de
> C++, c'est exclus.
Et quelle est la différence ? Il te faut bien une vtable, ou
quelque chose d'équivalent, pour chaque interface dont tu
dérives.
La seule différence réele que je vois, c'est que la JVM
a deux instructions différentes : invokeinterface et
invokevirtual, selon qu'on appelle un fonction à travers une
interface ou non. Quand tu invoques à travers un pointeur à une
classe, donc, la VM ou le compilateur sait d'avance qu'il n'a
pas besoin d'ajuster le pointeur. Quand tu invoques à travers
une interface, en revanche, qu'est-ce que la VM fait de moins
que quand tu appelles une fonction C++ depuis une classe de base
virtuelle ?
Ensuite, évidemment, il y a des questions
d'optimisation. Où Java a l'avantage par rapport à certains
compilateurs C++ d'avoir accès aux données propre à l'exécution.
Dans la pratique, j'admets qu'il peut y avoir des cas assez
rares où y insister serait un peu exagérer. Mais quand même :
en dehors des cas d'inversion d'appel,
où tu n'as pas de
contrat, l'idiome classique serait :
class Base // ou Interface, comme tu veux...
{
public:
Base* function()
{
// ...
return doFunction() ;
}
private:
virtual Base* doFunction() = 0 ;
} ;
class Derived : public Base
{ // ou class Implementation, si tu préfère.
public:
Derived* function() // Si on veut ; dans mon exp érience,
{ // c'est ra rement nécessaire.
// ...
}
private:
virtual Base* doFunction()
{
return function() ;
}
} ;
En fait, j'ai utilisé ce modèle beaucoup plus souvent pour des
raisons d'optimisation que pour des raisons de typage,
par
exemple, quand la fonction publique est operator[] sur une
classe qui implémente quelque chose comme vector (où le coût
d'un appel virtuel, et surtout l'impossibilité de générer la
fonction inline que cela implique, pourrait avoir des
consequences réeles sur les performances des applications).
Sinon, typiquement, la fonction publique dans Base s'appellera
quelque chose comme getBase, et celle dans Derived getDerived.
> > > mais cela rajoute encore des pointeurs des des ajustements
> > > d'offset. Au final tous ces calculs intermediaires
> > > ralentissent la resolution des appels virtuels de facon
> > > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > > rapport a un appel virtuel avec heritage simple,
> > > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > > au total si on mixe les deux, ce qui serait l'approche
> > > correcte. Je ne pense pas que les autres compilateur font
> > > mieux et je trouve deja pas mal compte tenu de tout ce que
> > > le compilateur doit generer.
> > Et alors ? Est-ce que tu connais des applications où ça fait
> > une différence ?
> oui, presque toutes celles sur lesquelles je travaille. C'est
> tout simplement la difference entre une application qui donne
> un resultat et rien du tout. Le cas le plus fameux ici, c'est
> le simulateur d'accelerateur. La version (ancienne) Fortran
> est environ 10 fois plus rapide que la version (recente)
> ecrite en C++, du coup cette derniere n'est jamais utilisee
> (les temps de calculs etant deja en heures voir en jours pour
> la version Fortran). L'autre probleme vient de la complexite
> du code C++ que personne ne veut reprendre.
Mais qu'est-ce que tu fais dans tes fonctions ? Quasiment rien,
alors.
Et alors, est-ce que le problème n'est pas plutôt du à la
virtualité en soi, qui empêche (en général) la génération
inline, et non à l'héritage multiple. Aussi, n'oublie pas que
même aujourd'hui, les compilateurs Fortran optimisent bien mieux
que ceux de C++. (Si tu es concerné par le calcul numérique, et
des performances, je conseille très fortement la lecture de
Barton et Nackman. Qui utilise intensement l'héritage multiple,
sans que ça ait un impact négatif sur les performancese.)
> > Je me suis servi parfois des graphes d'héritage à peine
> > croyable (à cause des contraints techniques liés à la façon
> > qu'on générait le code), et ça n'a jamais eu un impact
> > mesurable sur les performances de l'application.
> Un facteur 10 n'est pas negligeable lorsque les calculs
> prennent des heures, voir des jours pour les versions non
> polymorphique. Mais c'est vrai que dans la plupart des
> application, on ne voit probablement pas grand chose.
Le facteur 10, c'est dans l'appel de la fonction, tout au plus.
Comment est-ce que je peux me documenter sur ce que toi, tu
entends par un « vrai » héritage multiple ?
en quoi est-ce que l'héritage multiple de C++ (ou
de Java) n'est pas vrai ?
> Un point de depart possible, c'est le papier sur le modele
> objet de C++ que j'avais commence en 2005 ou le papier de
> Nathan Sidwell qui si je me souviens bien decrivait celui de
> g++.
Papiers qui se trouvent où.
> le D&E est pas mal aussi si on lit entre les lignes.
Je l'ai lu il y a un certain temps. J'ai aussi discuté avec
Stroustrup une ou deux fois, et avec les auteurs du compilateur
EDG et les auteurs de Sun CC. Autant que j'ai pu comprendre,
tous, ils considèrent l'héritage multiple un C++ un « vrai »
héritage multiple.
> L'heritage multiple de Java peut
> se faire en C assez facilement, et sans besoin de code
> supplementaire et avec une excellente performance. Celui de
> C++, c'est exclus.
Et quelle est la différence ? Il te faut bien une vtable, ou
quelque chose d'équivalent, pour chaque interface dont tu
dérives.
La seule différence réele que je vois, c'est que la JVM
a deux instructions différentes : invokeinterface et
invokevirtual, selon qu'on appelle un fonction à travers une
interface ou non. Quand tu invoques à travers un pointeur à une
classe, donc, la VM ou le compilateur sait d'avance qu'il n'a
pas besoin d'ajuster le pointeur. Quand tu invoques à travers
une interface, en revanche, qu'est-ce que la VM fait de moins
que quand tu appelles une fonction C++ depuis une classe de base
virtuelle ?
Ensuite, évidemment, il y a des questions
d'optimisation. Où Java a l'avantage par rapport à certains
compilateurs C++ d'avoir accès aux données propre à l'exécution.
Dans la pratique, j'admets qu'il peut y avoir des cas assez
rares où y insister serait un peu exagérer. Mais quand même :
en dehors des cas d'inversion d'appel,
où tu n'as pas de
contrat, l'idiome classique serait :
class Base // ou Interface, comme tu veux...
{
public:
Base* function()
{
// ...
return doFunction() ;
}
private:
virtual Base* doFunction() = 0 ;
} ;
class Derived : public Base
{ // ou class Implementation, si tu préfère.
public:
Derived* function() // Si on veut ; dans mon exp érience,
{ // c'est ra rement nécessaire.
// ...
}
private:
virtual Base* doFunction()
{
return function() ;
}
} ;
En fait, j'ai utilisé ce modèle beaucoup plus souvent pour des
raisons d'optimisation que pour des raisons de typage,
par
exemple, quand la fonction publique est operator[] sur une
classe qui implémente quelque chose comme vector (où le coût
d'un appel virtuel, et surtout l'impossibilité de générer la
fonction inline que cela implique, pourrait avoir des
consequences réeles sur les performances des applications).
Sinon, typiquement, la fonction publique dans Base s'appellera
quelque chose comme getBase, et celle dans Derived getDerived.
> > > mais cela rajoute encore des pointeurs des des ajustements
> > > d'offset. Au final tous ces calculs intermediaires
> > > ralentissent la resolution des appels virtuels de facon
> > > non negligeable. Mes mesures sur gcc 4.2 montrent que par
> > > rapport a un appel virtuel avec heritage simple,
> > > l'heritage multiple de 4 interfaces ralentit d'un facteur
> > > ±2 l'appel, et l'heritage virtuel d'un facteur ±4. Soit ±8
> > > au total si on mixe les deux, ce qui serait l'approche
> > > correcte. Je ne pense pas que les autres compilateur font
> > > mieux et je trouve deja pas mal compte tenu de tout ce que
> > > le compilateur doit generer.
> > Et alors ? Est-ce que tu connais des applications où ça fait
> > une différence ?
> oui, presque toutes celles sur lesquelles je travaille. C'est
> tout simplement la difference entre une application qui donne
> un resultat et rien du tout. Le cas le plus fameux ici, c'est
> le simulateur d'accelerateur. La version (ancienne) Fortran
> est environ 10 fois plus rapide que la version (recente)
> ecrite en C++, du coup cette derniere n'est jamais utilisee
> (les temps de calculs etant deja en heures voir en jours pour
> la version Fortran). L'autre probleme vient de la complexite
> du code C++ que personne ne veut reprendre.
Mais qu'est-ce que tu fais dans tes fonctions ? Quasiment rien,
alors.
Et alors, est-ce que le problème n'est pas plutôt du à la
virtualité en soi, qui empêche (en général) la génération
inline, et non à l'héritage multiple. Aussi, n'oublie pas que
même aujourd'hui, les compilateurs Fortran optimisent bien mieux
que ceux de C++. (Si tu es concerné par le calcul numérique, et
des performances, je conseille très fortement la lecture de
Barton et Nackman. Qui utilise intensement l'héritage multiple,
sans que ça ait un impact négatif sur les performancese.)
> > Je me suis servi parfois des graphes d'héritage à peine
> > croyable (à cause des contraints techniques liés à la façon
> > qu'on générait le code), et ça n'a jamais eu un impact
> > mesurable sur les performances de l'application.
> Un facteur 10 n'est pas negligeable lorsque les calculs
> prennent des heures, voir des jours pour les versions non
> polymorphique. Mais c'est vrai que dans la plupart des
> application, on ne voit probablement pas grand chose.
Le facteur 10, c'est dans l'appel de la fonction, tout au plus.
Comment est-ce que je peux me documenter sur ce que toi, tu
entends par un « vrai » héritage multiple ?
en quoi est-ce que l'héritage multiple de C++ (ou
de Java) n'est pas vrai ?
> Un point de depart possible, c'est le papier sur le modele
> objet de C++ que j'avais commence en 2005 ou le papier de
> Nathan Sidwell qui si je me souviens bien decrivait celui de
> g++.
Papiers qui se trouvent où.
> le D&E est pas mal aussi si on lit entre les lignes.
Je l'ai lu il y a un certain temps. J'ai aussi discuté avec
Stroustrup une ou deux fois, et avec les auteurs du compilateur
EDG et les auteurs de Sun CC. Autant que j'ai pu comprendre,
tous, ils considèrent l'héritage multiple un C++ un « vrai »
héritage multiple.
> L'heritage multiple de Java peut
> se faire en C assez facilement, et sans besoin de code
> supplementaire et avec une excellente performance. Celui de
> C++, c'est exclus.
Et quelle est la différence ? Il te faut bien une vtable, ou
quelque chose d'équivalent, pour chaque interface dont tu
dérives.
La seule différence réele que je vois, c'est que la JVM
a deux instructions différentes : invokeinterface et
invokevirtual, selon qu'on appelle un fonction à travers une
interface ou non. Quand tu invoques à travers un pointeur à une
classe, donc, la VM ou le compilateur sait d'avance qu'il n'a
pas besoin d'ajuster le pointeur. Quand tu invoques à travers
une interface, en revanche, qu'est-ce que la VM fait de moins
que quand tu appelles une fonction C++ depuis une classe de base
virtuelle ?
Ensuite, évidemment, il y a des questions
d'optimisation. Où Java a l'avantage par rapport à certains
compilateurs C++ d'avoir accès aux données propre à l'exécution.