On 31 oct, 11:07, James Kanze wrote:
> 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 Implementatio n, si tu préfère.
> public:
> Derived* function() // Si on veut ; dans mon ex périence,
> { // c'est rarement 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,
C'est pourtant la facon classique de faire. De mon cote je
prefere la facon suivante, ca pollue moins l'interface:
struct Interface {
virtual Interface* function() = 0 ;
};
class Concrete : public virtual Interface {
Concrete* func() {
func_pre();
...
func_post();
}
protected:
void func_pre() { ... }
void func_post() { ... }
private:
virtual Interface* function() { return func(); }
};
> 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).
Ce n'est pas la seule raison. Ou mets-tu les contrats de la
classe si tu n'as pas de version publique non virtuelle dans
ta classe?
L'avantage de ma version (surtout quand on doit l'expliquer a
des etudiants avec peu de background OO), c'est que
l'Interface est equivalente a une API C, ni plus ni moins, et
que la partie publique de la classe est equivalent a une
classe monomorphique. Apres on rajoute la colle entre
l'interface et la classe qui devient polymorphique et
exportable.
> Sinon, typiquement, la fonction publique dans Base
> s'appellera quelque chose comme getBase, et celle dans
> Derived getDerived.
? J'utilise tres tres rarement des getters ou des settesr si
c'est ce dont tu parles. Je n'aime pas ca.
> 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
Je considere qu'un appel virtuel en presence d'heritage simple
comme etant l'unite a payer puisque je favorise la
flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
ou C).
> 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 ?
C++ supporte l'heritage multiple d'implementation (ce que j'ai
appelle "vrai" pour simplifier)
> > 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.
Dans le cas de Java, elle peut etre compacte et unique.
> 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 ?
tout est explique dans le papier de Nathan, le mien ou l'ABI
du C++ a la section virtual call.
> 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.
Quand on utilise les interfaces, il ne peut pas faire grand
chose de plus que C++.
On 31 oct, 11:07, James Kanze <james.ka...@gmail.com> wrote:
> 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 Implementatio n, si tu préfère.
> public:
> Derived* function() // Si on veut ; dans mon ex périence,
> { // c'est rarement 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,
C'est pourtant la facon classique de faire. De mon cote je
prefere la facon suivante, ca pollue moins l'interface:
struct Interface {
virtual Interface* function() = 0 ;
};
class Concrete : public virtual Interface {
Concrete* func() {
func_pre();
...
func_post();
}
protected:
void func_pre() { ... }
void func_post() { ... }
private:
virtual Interface* function() { return func(); }
};
> 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).
Ce n'est pas la seule raison. Ou mets-tu les contrats de la
classe si tu n'as pas de version publique non virtuelle dans
ta classe?
L'avantage de ma version (surtout quand on doit l'expliquer a
des etudiants avec peu de background OO), c'est que
l'Interface est equivalente a une API C, ni plus ni moins, et
que la partie publique de la classe est equivalent a une
classe monomorphique. Apres on rajoute la colle entre
l'interface et la classe qui devient polymorphique et
exportable.
> Sinon, typiquement, la fonction publique dans Base
> s'appellera quelque chose comme getBase, et celle dans
> Derived getDerived.
? J'utilise tres tres rarement des getters ou des settesr si
c'est ce dont tu parles. Je n'aime pas ca.
> 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
Je considere qu'un appel virtuel en presence d'heritage simple
comme etant l'unite a payer puisque je favorise la
flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
ou C).
> 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 ?
C++ supporte l'heritage multiple d'implementation (ce que j'ai
appelle "vrai" pour simplifier)
> > 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.
Dans le cas de Java, elle peut etre compacte et unique.
> 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 ?
tout est explique dans le papier de Nathan, le mien ou l'ABI
du C++ a la section virtual call.
> 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.
Quand on utilise les interfaces, il ne peut pas faire grand
chose de plus que C++.
On 31 oct, 11:07, James Kanze wrote:
> 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 Implementatio n, si tu préfère.
> public:
> Derived* function() // Si on veut ; dans mon ex périence,
> { // c'est rarement 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,
C'est pourtant la facon classique de faire. De mon cote je
prefere la facon suivante, ca pollue moins l'interface:
struct Interface {
virtual Interface* function() = 0 ;
};
class Concrete : public virtual Interface {
Concrete* func() {
func_pre();
...
func_post();
}
protected:
void func_pre() { ... }
void func_post() { ... }
private:
virtual Interface* function() { return func(); }
};
> 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).
Ce n'est pas la seule raison. Ou mets-tu les contrats de la
classe si tu n'as pas de version publique non virtuelle dans
ta classe?
L'avantage de ma version (surtout quand on doit l'expliquer a
des etudiants avec peu de background OO), c'est que
l'Interface est equivalente a une API C, ni plus ni moins, et
que la partie publique de la classe est equivalent a une
classe monomorphique. Apres on rajoute la colle entre
l'interface et la classe qui devient polymorphique et
exportable.
> Sinon, typiquement, la fonction publique dans Base
> s'appellera quelque chose comme getBase, et celle dans
> Derived getDerived.
? J'utilise tres tres rarement des getters ou des settesr si
c'est ce dont tu parles. Je n'aime pas ca.
> 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
Je considere qu'un appel virtuel en presence d'heritage simple
comme etant l'unite a payer puisque je favorise la
flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
ou C).
> 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 ?
C++ supporte l'heritage multiple d'implementation (ce que j'ai
appelle "vrai" pour simplifier)
> > 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.
Dans le cas de Java, elle peut etre compacte et unique.
> 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 ?
tout est explique dans le papier de Nathan, le mien ou l'ABI
du C++ a la section virtual call.
> 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.
Quand on utilise les interfaces, il ne peut pas faire grand
chose de plus que C++.
Mais si tu copies d'après les sources de Linux ou Windows, ou depuis google
groups venant de divers gurus (Stroustrup et autres), tu ne feras
certainement pas mieux en partant de zéro et tu perdras du temps.
Mais si tu copies d'après les sources de Linux ou Windows, ou depuis google
groups venant de divers gurus (Stroustrup et autres), tu ne feras
certainement pas mieux en partant de zéro et tu perdras du temps.
Mais si tu copies d'après les sources de Linux ou Windows, ou depuis google
groups venant de divers gurus (Stroustrup et autres), tu ne feras
certainement pas mieux en partant de zéro et tu perdras du temps.
> C'est pourtant la facon classique de faire. De mon cote je
> prefere la facon suivante, ca pollue moins l'interface:
> struct Interface {
> virtual Interface* function() = 0 ;
> };
> class Concrete : public virtual Interface {
> Concrete* func() {
> func_pre();
> ...
> func_post();
> }
> protected:
> void func_pre() { ... }
> void func_post() { ... }
> private:
> virtual Interface* function() { return func(); }
> };
Ce qui ne fait pas du tout la même chose. Le contrat, c'est bien
l'interface qui le définit, et qui donc l'enforce. Compter sur
la classe dérivée de l'enforcer ; autant ne pas l'avoir.
> > 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
> Je considere qu'un appel virtuel en presence d'heritage simple
> comme etant l'unite a payer puisque je favorise la
> flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
> ou C).
Oui, mais c'est elle qui introduit la plus grande différence
dans les temps d'exécution.
> C'est pourtant la facon classique de faire. De mon cote je
> prefere la facon suivante, ca pollue moins l'interface:
> struct Interface {
> virtual Interface* function() = 0 ;
> };
> class Concrete : public virtual Interface {
> Concrete* func() {
> func_pre();
> ...
> func_post();
> }
> protected:
> void func_pre() { ... }
> void func_post() { ... }
> private:
> virtual Interface* function() { return func(); }
> };
Ce qui ne fait pas du tout la même chose. Le contrat, c'est bien
l'interface qui le définit, et qui donc l'enforce. Compter sur
la classe dérivée de l'enforcer ; autant ne pas l'avoir.
> > 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
> Je considere qu'un appel virtuel en presence d'heritage simple
> comme etant l'unite a payer puisque je favorise la
> flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
> ou C).
Oui, mais c'est elle qui introduit la plus grande différence
dans les temps d'exécution.
> C'est pourtant la facon classique de faire. De mon cote je
> prefere la facon suivante, ca pollue moins l'interface:
> struct Interface {
> virtual Interface* function() = 0 ;
> };
> class Concrete : public virtual Interface {
> Concrete* func() {
> func_pre();
> ...
> func_post();
> }
> protected:
> void func_pre() { ... }
> void func_post() { ... }
> private:
> virtual Interface* function() { return func(); }
> };
Ce qui ne fait pas du tout la même chose. Le contrat, c'est bien
l'interface qui le définit, et qui donc l'enforce. Compter sur
la classe dérivée de l'enforcer ; autant ne pas l'avoir.
> > 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
> Je considere qu'un appel virtuel en presence d'heritage simple
> comme etant l'unite a payer puisque je favorise la
> flexibilite, donc le polymorphisme (sinon j'utiliserais Fortan
> ou C).
Oui, mais c'est elle qui introduit la plus grande différence
dans les temps d'exécution.
Uun développeur ne crée d'ailleurs jamais d'algorithme mais ne fait
qu'utiliser des algorithme existants en les adaptant.
Uun développeur ne crée d'ailleurs jamais d'algorithme mais ne fait
qu'utiliser des algorithme existants en les adaptant.
Uun développeur ne crée d'ailleurs jamais d'algorithme mais ne fait
qu'utiliser des algorithme existants en les adaptant.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
D'ailleurs on peut écrire : "0123456789ABCDEF"[i].
Je peux te garantir que ceci trouble même certains programmeurs C très
expérimentés.
On Oct 30, 4:31 pm, ld 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++.
On Oct 30, 4:31 pm, ld <Laurent.Den...@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++.
On Oct 30, 4:31 pm, ld 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++.
Michael DOUBEZ a écrit :Wykaaa a écrit :[snip]
Je voulais dire, en fait :
pt étant un nom de tableau peut-on écrire pt++ ?
La réponse est évidemment non puisqu'un nom de tableau est un pointeur
constant sur le premier élément du tableau.
Comme d'autre l'ont signalé, le om d'un tableau est de type tableau et
non pas de type pointer. Pour des raisons historiques (K&R C), le tableau
se converti implicitement en pointeur mais il reste un tableau.
D'ailleur:
int * const ptr;
const int tab[42];
int * const ptr=tab;
sizeoff(ptr)!=sizeof(tab)
Mais tab[5] == *(tab+5)
Donc ta dernière phrase joue sur les mots car, dans la partie droite de ma
comparaison, le nom du tableau est bien utilisé comme un pointeur !
Et là c'est encore plus fort car tab+5 signifie :
&tab[0] + 5*sizeof(type_des_elements_du_tableau).
Michael DOUBEZ a écrit :
Wykaaa a écrit :
[snip]
Je voulais dire, en fait :
pt étant un nom de tableau peut-on écrire pt++ ?
La réponse est évidemment non puisqu'un nom de tableau est un pointeur
constant sur le premier élément du tableau.
Comme d'autre l'ont signalé, le om d'un tableau est de type tableau et
non pas de type pointer. Pour des raisons historiques (K&R C), le tableau
se converti implicitement en pointeur mais il reste un tableau.
D'ailleur:
int * const ptr;
const int tab[42];
int * const ptr=tab;
sizeoff(ptr)!=sizeof(tab)
Mais tab[5] == *(tab+5)
Donc ta dernière phrase joue sur les mots car, dans la partie droite de ma
comparaison, le nom du tableau est bien utilisé comme un pointeur !
Et là c'est encore plus fort car tab+5 signifie :
&tab[0] + 5*sizeof(type_des_elements_du_tableau).
Michael DOUBEZ a écrit :Wykaaa a écrit :[snip]
Je voulais dire, en fait :
pt étant un nom de tableau peut-on écrire pt++ ?
La réponse est évidemment non puisqu'un nom de tableau est un pointeur
constant sur le premier élément du tableau.
Comme d'autre l'ont signalé, le om d'un tableau est de type tableau et
non pas de type pointer. Pour des raisons historiques (K&R C), le tableau
se converti implicitement en pointeur mais il reste un tableau.
D'ailleur:
int * const ptr;
const int tab[42];
int * const ptr=tab;
sizeoff(ptr)!=sizeof(tab)
Mais tab[5] == *(tab+5)
Donc ta dernière phrase joue sur les mots car, dans la partie droite de ma
comparaison, le nom du tableau est bien utilisé comme un pointeur !
Et là c'est encore plus fort car tab+5 signifie :
&tab[0] + 5*sizeof(type_des_elements_du_tableau).
Wykaaa writes:
> Uun développeur ne crée d'ailleurs jamais d'algorithme mais
> ne fait qu'utiliser des algorithme existants en les
> adaptant.
Ce n'est pas un programmeur alors.
Wykaaa <wyk...@yahoo.fr> writes:
> Uun développeur ne crée d'ailleurs jamais d'algorithme mais
> ne fait qu'utiliser des algorithme existants en les
> adaptant.
Ce n'est pas un programmeur alors.
Wykaaa writes:
> Uun développeur ne crée d'ailleurs jamais d'algorithme mais
> ne fait qu'utiliser des algorithme existants en les
> adaptant.
Ce n'est pas un programmeur alors.
Jean-Marc Bourguet a écrit :Wykaaa writes:
Les tableaux C sont déjà une source de confusion assez grande comme
cela; faire croire que le nom d'un tableau a un type pointeur ne fait
qu'augmenter la confusion -- en particulier en C++.
Désolé, mais "un type pointeur", ça ne veut rien dire puisque les pointeurs
sont... typés.
Je voulais dire que le nom de tableau est manipulé comme un pointeur
(d'ailleurs du type des éléments).
S'il n'y avait pas cette "équivalence" pointeur/tableau, il n'y aurait pas
besoin des [] pour indiquer à delete que le pointeur pointe sur un tableau
alloué dynamiquement afin d'appeler les destructeurs pour chaque instance.
Denis Ritchie qui parlait de ces traits de langage (en y incluant les
chaînes de caractères) dans l'HOPL (History Of Programming Lnguages) de
1993 et en vantant les mérites de "l'homogénéisation" entre chaînes de
caractères, tableaux, etc. en C par rapport à PL/1 ne devrait finalement
pas être si fier car, au total, tout ceci ne peut pas toujours être
optimisé (comme il le reconnaissait) et sème une grande confusion pour les
débutants (et même souvent pour les développeurs expérimentés).
Jean-Marc Bourguet a écrit :
Wykaaa <wykaaa@yahoo.fr> writes:
Les tableaux C sont déjà une source de confusion assez grande comme
cela; faire croire que le nom d'un tableau a un type pointeur ne fait
qu'augmenter la confusion -- en particulier en C++.
Désolé, mais "un type pointeur", ça ne veut rien dire puisque les pointeurs
sont... typés.
Je voulais dire que le nom de tableau est manipulé comme un pointeur
(d'ailleurs du type des éléments).
S'il n'y avait pas cette "équivalence" pointeur/tableau, il n'y aurait pas
besoin des [] pour indiquer à delete que le pointeur pointe sur un tableau
alloué dynamiquement afin d'appeler les destructeurs pour chaque instance.
Denis Ritchie qui parlait de ces traits de langage (en y incluant les
chaînes de caractères) dans l'HOPL (History Of Programming Lnguages) de
1993 et en vantant les mérites de "l'homogénéisation" entre chaînes de
caractères, tableaux, etc. en C par rapport à PL/1 ne devrait finalement
pas être si fier car, au total, tout ceci ne peut pas toujours être
optimisé (comme il le reconnaissait) et sème une grande confusion pour les
débutants (et même souvent pour les développeurs expérimentés).
Jean-Marc Bourguet a écrit :Wykaaa writes:
Les tableaux C sont déjà une source de confusion assez grande comme
cela; faire croire que le nom d'un tableau a un type pointeur ne fait
qu'augmenter la confusion -- en particulier en C++.
Désolé, mais "un type pointeur", ça ne veut rien dire puisque les pointeurs
sont... typés.
Je voulais dire que le nom de tableau est manipulé comme un pointeur
(d'ailleurs du type des éléments).
S'il n'y avait pas cette "équivalence" pointeur/tableau, il n'y aurait pas
besoin des [] pour indiquer à delete que le pointeur pointe sur un tableau
alloué dynamiquement afin d'appeler les destructeurs pour chaque instance.
Denis Ritchie qui parlait de ces traits de langage (en y incluant les
chaînes de caractères) dans l'HOPL (History Of Programming Lnguages) de
1993 et en vantant les mérites de "l'homogénéisation" entre chaînes de
caractères, tableaux, etc. en C par rapport à PL/1 ne devrait finalement
pas être si fier car, au total, tout ceci ne peut pas toujours être
optimisé (comme il le reconnaissait) et sème une grande confusion pour les
débutants (et même souvent pour les développeurs expérimentés).
On 31 oct, 20:38, James Kanze wrote:
> > C'est pourtant la facon classique de faire. De mon cote je
> > prefere la facon suivante, ca pollue moins l'interface:
> > struct Interface {
> > virtual Interface* function() = 0 ;
> > };
> > class Concrete : public virtual Interface {
> > Concrete* func() {
> > func_pre();
> > ...
> > func_post();
> > }
> > protected:
> > void func_pre() { ... }
> > void func_post() { ... }
> > private:
> > virtual Interface* function() { return func(); }
> > };
> Ce qui ne fait pas du tout la même chose. Le contrat, c'est
> bien l'interface qui le définit, et qui donc l'enforce.
> Compter sur la classe dérivée de l'enforcer ; autant ne pas
> l'avoir.
Ca depend de quoi on parle. Si on parle des interfaces
a-la-Java, il n'y a pas le choix puisque les interfaces n'ont
pas de donnees (et en principe pas d'implementation), donc tes
//... dans Base::function ne peuvent etre que tres limites.
Si on parle des contrat a-la-Eiffel, les contrats sont dans
toutes les classes, inclues les interfaces avec la regle de
disjonction pour l'heritage des pre-conditions et de
conjonction pour l'heritage des post-conditions. Donc en C++
il faudrait faire:
bool Derived::func_pre(bool assert) {
bool contract = func_pre_cond || Base::func_pre(false);
if (assert) assert( contract == true );
return contract;
}
bool Derived::func_post(bool assert) {
bool contract = func_post_cond && Base::func_post(false);
if (assert) assert( contract == true );
return contract;
}
Ca fait rapidement beaucoup de code! Donc je pars du principe
que chaque classe est responsable de ses donnees et c'est
tout.
Ce qui veut dire que si les appels virtuels se chaines, les
contrats aussi sous forme conjonctive (toujours). Ce n'est pas
l'ideal pour determiner la responsabilite du fautif, mais
c'est suffisant dans la plus part des cas. De toute facon, la
facon de Eiffel non plus n'est pas ideale pour determiner
correctement les responsabilites, voir:
R.B. Findler, M. Latendresse, and M. Felleisen.
"Behavioral Contracts and Behavioral Subtyping".
9th ACM Sigsoft International Symposium on Foundations
of Software Engineering, 2001
Donc je prefere favoriser la localite (cohesion) des contrats.
> > > 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
> > Je considere qu'un appel virtuel en presence d'heritage
> > simple comme etant l'unite a payer puisque je favorise la
> > flexibilite, donc le polymorphisme (sinon j'utiliserais
> > Fortan ou C).
> Oui, mais c'est elle qui introduit la plus grande différence
> dans les temps d'exécution.
Certe, la virtualite par rapport a l'inlining, c'est un
facteur 2-3 pour une fonction triviale.
Mais c'est le prix a paye pour la flexibilite du code
(reduction du couplage) et c'est inevitable.
Tandis que le cout introduit par l'heritage multiple
d'implementations pourrait etre optimise et ne pas ralentir la
virtualite dans le cas particulier de l'heritage multiple
d'interfaces (pas de donnees membres).
On 31 oct, 20:38, James Kanze <james.ka...@gmail.com> wrote:
> > C'est pourtant la facon classique de faire. De mon cote je
> > prefere la facon suivante, ca pollue moins l'interface:
> > struct Interface {
> > virtual Interface* function() = 0 ;
> > };
> > class Concrete : public virtual Interface {
> > Concrete* func() {
> > func_pre();
> > ...
> > func_post();
> > }
> > protected:
> > void func_pre() { ... }
> > void func_post() { ... }
> > private:
> > virtual Interface* function() { return func(); }
> > };
> Ce qui ne fait pas du tout la même chose. Le contrat, c'est
> bien l'interface qui le définit, et qui donc l'enforce.
> Compter sur la classe dérivée de l'enforcer ; autant ne pas
> l'avoir.
Ca depend de quoi on parle. Si on parle des interfaces
a-la-Java, il n'y a pas le choix puisque les interfaces n'ont
pas de donnees (et en principe pas d'implementation), donc tes
//... dans Base::function ne peuvent etre que tres limites.
Si on parle des contrat a-la-Eiffel, les contrats sont dans
toutes les classes, inclues les interfaces avec la regle de
disjonction pour l'heritage des pre-conditions et de
conjonction pour l'heritage des post-conditions. Donc en C++
il faudrait faire:
bool Derived::func_pre(bool assert) {
bool contract = func_pre_cond || Base::func_pre(false);
if (assert) assert( contract == true );
return contract;
}
bool Derived::func_post(bool assert) {
bool contract = func_post_cond && Base::func_post(false);
if (assert) assert( contract == true );
return contract;
}
Ca fait rapidement beaucoup de code! Donc je pars du principe
que chaque classe est responsable de ses donnees et c'est
tout.
Ce qui veut dire que si les appels virtuels se chaines, les
contrats aussi sous forme conjonctive (toujours). Ce n'est pas
l'ideal pour determiner la responsabilite du fautif, mais
c'est suffisant dans la plus part des cas. De toute facon, la
facon de Eiffel non plus n'est pas ideale pour determiner
correctement les responsabilites, voir:
R.B. Findler, M. Latendresse, and M. Felleisen.
"Behavioral Contracts and Behavioral Subtyping".
9th ACM Sigsoft International Symposium on Foundations
of Software Engineering, 2001
Donc je prefere favoriser la localite (cohesion) des contrats.
> > > 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
> > Je considere qu'un appel virtuel en presence d'heritage
> > simple comme etant l'unite a payer puisque je favorise la
> > flexibilite, donc le polymorphisme (sinon j'utiliserais
> > Fortan ou C).
> Oui, mais c'est elle qui introduit la plus grande différence
> dans les temps d'exécution.
Certe, la virtualite par rapport a l'inlining, c'est un
facteur 2-3 pour une fonction triviale.
Mais c'est le prix a paye pour la flexibilite du code
(reduction du couplage) et c'est inevitable.
Tandis que le cout introduit par l'heritage multiple
d'implementations pourrait etre optimise et ne pas ralentir la
virtualite dans le cas particulier de l'heritage multiple
d'interfaces (pas de donnees membres).
On 31 oct, 20:38, James Kanze wrote:
> > C'est pourtant la facon classique de faire. De mon cote je
> > prefere la facon suivante, ca pollue moins l'interface:
> > struct Interface {
> > virtual Interface* function() = 0 ;
> > };
> > class Concrete : public virtual Interface {
> > Concrete* func() {
> > func_pre();
> > ...
> > func_post();
> > }
> > protected:
> > void func_pre() { ... }
> > void func_post() { ... }
> > private:
> > virtual Interface* function() { return func(); }
> > };
> Ce qui ne fait pas du tout la même chose. Le contrat, c'est
> bien l'interface qui le définit, et qui donc l'enforce.
> Compter sur la classe dérivée de l'enforcer ; autant ne pas
> l'avoir.
Ca depend de quoi on parle. Si on parle des interfaces
a-la-Java, il n'y a pas le choix puisque les interfaces n'ont
pas de donnees (et en principe pas d'implementation), donc tes
//... dans Base::function ne peuvent etre que tres limites.
Si on parle des contrat a-la-Eiffel, les contrats sont dans
toutes les classes, inclues les interfaces avec la regle de
disjonction pour l'heritage des pre-conditions et de
conjonction pour l'heritage des post-conditions. Donc en C++
il faudrait faire:
bool Derived::func_pre(bool assert) {
bool contract = func_pre_cond || Base::func_pre(false);
if (assert) assert( contract == true );
return contract;
}
bool Derived::func_post(bool assert) {
bool contract = func_post_cond && Base::func_post(false);
if (assert) assert( contract == true );
return contract;
}
Ca fait rapidement beaucoup de code! Donc je pars du principe
que chaque classe est responsable de ses donnees et c'est
tout.
Ce qui veut dire que si les appels virtuels se chaines, les
contrats aussi sous forme conjonctive (toujours). Ce n'est pas
l'ideal pour determiner la responsabilite du fautif, mais
c'est suffisant dans la plus part des cas. De toute facon, la
facon de Eiffel non plus n'est pas ideale pour determiner
correctement les responsabilites, voir:
R.B. Findler, M. Latendresse, and M. Felleisen.
"Behavioral Contracts and Behavioral Subtyping".
9th ACM Sigsoft International Symposium on Foundations
of Software Engineering, 2001
Donc je prefere favoriser la localite (cohesion) des contrats.
> > > 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
> > Je considere qu'un appel virtuel en presence d'heritage
> > simple comme etant l'unite a payer puisque je favorise la
> > flexibilite, donc le polymorphisme (sinon j'utiliserais
> > Fortan ou C).
> Oui, mais c'est elle qui introduit la plus grande différence
> dans les temps d'exécution.
Certe, la virtualite par rapport a l'inlining, c'est un
facteur 2-3 pour une fonction triviale.
Mais c'est le prix a paye pour la flexibilite du code
(reduction du couplage) et c'est inevitable.
Tandis que le cout introduit par l'heritage multiple
d'implementations pourrait etre optimise et ne pas ralentir la
virtualite dans le cas particulier de l'heritage multiple
d'interfaces (pas de donnees membres).