Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

POO - Hériter Rectangle d'un Square

42 réponses
Avatar
David FLEURY
Bonjour,

(posté ailleurs mais sans réponse, je tente ma chance ici, étant plus
porté sur le C++)

dans http://www.objectmentor.com/resources/articles/lsp.pdf à propos du
LSP, l'exemple est donné à base d'un Square héritant d'un Rectangle (à
ne pas faire donc)

Auriez-vous une idée d'exemple qui poserait problème pour le cas d'un
Rectangle héritant d'un Square (ou si, à votre avis, le deuxième exemple
que j'ai codé suffit)?

Voici l'argument mis en avant dans l'article (en C++), je reprends
l'exemple donné dans l'article original :
(Un Square héritant d'un rectangle)

// --------------------------------------------------------------------
#include <iostream>

using namespace std;

class Rectangle {
public:
Rectangle( int w = 0, int h = 0 ) : width_(w), height_(h) {}

virtual int width() const { return width_; }
virtual int height() const { return height_; }

virtual void width( int w ) { width_ = w; }
virtual void height( int h ) { height_ = h; }

private:
int width_;
int height_;
};

class Square : public Rectangle {
public:
Square( int w = 0 )
{
width( w );
}

virtual void width( int w ) {
Rectangle::width( w ); Rectangle::height( w );
}

virtual void height( int h ) {
Rectangle::width( h ); Rectangle::height( h );
}
};

// -------- Utilisation du client -------
void g( Rectangle& r ) {
r.width( 5 );
r.height( 4 );

assert( r.width() * r.height() == 20 );
}

int main()
{
Rectangle r( 2, 3 );
g( r );

Square s( 4 );
g( s );
}

Est-ce que celui là pour l'autre sens serait recevable ?
(un Rectangle héritant d'un Square)

// ---------------------------------------------------------------------
#include <iostream>

using namespace std;

class Square {
public:
Square( int w = 0 ) { width( w ); }

int width() const { return width_; }
void width( int w ) { width_ = w; }

virtual double area() const { return width() * width(); }

private:
int width_;
};

class Rectangle : public Square {
public:
Rectangle( int w = 0, int h = 0 )
{
width( w );
height( h );
}

int height() const { return height_; }
void height( int h ) { height_ = h; }

virtual double area() const { return width() * height(); }

private:
int height_;
};

// -------- Utilisation du client -------
void g( Square& r ) {
r.width( 5 );

assert( r.area() == 25 );
}

int main()
{
Square s( 4 );
g( s );

Rectangle r( 2, 3 );
g( r );
}


Cordialement.

10 réponses

1 2 3 4 5
Avatar
JKB
Le Wed, 25 Apr 2012 22:52:21 +0000 (UTC),
Marc Espie écrivait :
In article <jn94np$pg1$,
Marc Boyer wrote:
Le 25-04-2012, Marc Espie a écrit :
In article <4f97efe8$0$21490$,
Laurent Georget wrote:
Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



En C++, tu peux faire ce que tu veux. Suffit de rajouter une couche
d'abstraction, et tu peux tres bien avoir un objet qui contiendra au choix
un Rectangle ou un Carre et qui mutera en fonction.

C'est pas "natif" au langage, mais il n'y a pas besoin, ca ne coutera
rien au runtime (evidemment le compilo va morfler par contre).



Enfin, entre avec une méthode qui retourne un nouvel objet
et une autre qui se modifie l'objet contenu, je ne vois pas
de grande différence, hormis le fait de cacher le type
au compilo.

Marc Boyer



Il n'y en a pas, j'en ai juste ma claque des gnagnagna habituels sur
`ah ca je peux faire avec mon vrai langage objet'. Le truc bien avec C++,
c'est qu'en vrai, ca ne t'impose a peu pres rien sur ce que tu fais, tu
peux inventer la semantique que tu veux pour coller sur ta syntaxe.

(le truc pas bien, c'est que tu peux VRAIMENT faire ce que tu veux, meme
et surtout si c'est n'importe quoi).



Ce n'est pas vraiment spécifique au C++. En C, c'est pareil. Et
comme il y a de plus en plus de gens sur le marché qui ne savent pas
programmer correctement (mais qui se figurent savoir), ce n'est pas
vraiment prêt à s'arranger.

Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
l'entier 0 et le const char * NULL :-( ).

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Jean-Marc Bourguet
JKB writes:

Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
savoir ce que l'on fait (et avoir un compilo C++ à peu près ex empt
de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
l'entier 0 et le const char * NULL :-( ).



Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr). Il y a des consequences "interessantes" avec la
surcharge et les templates.

g++ donne un warning quand on utilise NULL dans des contextes non
pointeurs, mais genere du code comme si c'etait 0 de specifie.

(En C NULL peut aussi etre definit comme (void*)0, c'est pas autorise en
C++).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index. html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
JKB
Le Thu, 26 Apr 2012 09:54:54 +0200,
Jean-Marc Bourguet écrivait :
JKB writes:

Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
l'entier 0 et le const char * NULL :-( ).



Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr). Il y a des consequences "interessantes" avec la
surcharge et les templates.



Sauf que même en forçant explicitement le type, le compilo n'en a
rien à faire. Avoir une méthode foo(const char *p) et une seconde
foo(int i) fait que foo(0) appelle la version cont char * même si je
prends la peine de coller foo((int) 0). Ce qui est assez amusant de
mon point de vue, c'est que foo(1) ne se pose pas la question (alors
que j'ai au moins une architecture sous la main où NULL ne vaut pas
0). La solution pas élégante a été de rajouter une méthode pour
gérer explicitement ce cas avec un argument supplémentaire. J'avoue,
ce n'est pas propre.

g++ donne un warning quand on utilise NULL dans des contextes non
pointeurs, mais genere du code comme si c'etait 0 de specifie.



Là, c'est l'inverse, et g++ ne râle pas (je compile toujours le C++
avec -Werror -Wall -Wextra).

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Jean-Marc Bourguet
JKB writes:

Le Thu, 26 Apr 2012 09:54:54 +0200,
Jean-Marc Bourguet écrivait :
JKB writes:

Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
l'entier 0 et le const char * NULL :-( ).



Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr). Il y a des consequences "interessantes" avec la
surcharge et les templates.



Sauf que même en forçant explicitement le type, le compilo n'e n a
rien à faire. Avoir une méthode foo(const char *p) et une seco nde
foo(int i) fait que foo(0) appelle la version cont char *



Etrange.

même si je
prends la peine de coller foo((int) 0).



Encore plus. Quelle version? Bon

#include <iostream>

struct C {
void f(int) {
std::cout << "f(int)n";
}

void f(char const*) {
std::cout << "f(char const*)n";
}
};

int main()
{
C c;
c.f(0);
}

affiche f(int) avec toutes celles que j'ai sous la main et que j'ai
essayees.

Ce qui est assez amusant de
mon point de vue, c'est que foo(1) ne se pose pas la question (alors
que j'ai au moins une architecture sous la main où NULL ne vaut pas
0).



La representation physique n'a pas d'importance. Dans le code, une
constante entiere de valeur 0 est un pointeur nul qui peut physiquement
etre represente autrement qu'un int de valeur 0.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index. html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Marc Boyer
Le 25-04-2012, Marc Espie a écrit :
In article <jn94np$pg1$,
Marc Boyer wrote:
Le 25-04-2012, Marc Espie a écrit :
In article <4f97efe8$0$21490$,
Laurent Georget wrote:
Mais est-ce qu'on
ne devrait pas avoir moyen de "remplacer" un objet par un autre, au lieu
de retourner un résultat ? Les langages objet que je connais (C++ et
Java, et aussi partiellement OCaml) ne sont pas vraiment friands de ce
genre de concepts.



En C++, tu peux faire ce que tu veux. Suffit de rajouter une couche
d'abstraction, et tu peux tres bien avoir un objet qui contiendra au choix
un Rectangle ou un Carre et qui mutera en fonction.

C'est pas "natif" au langage, mais il n'y a pas besoin, ca ne coutera
rien au runtime (evidemment le compilo va morfler par contre).



Enfin, entre avec une méthode qui retourne un nouvel objet
et une autre qui se modifie l'objet contenu, je ne vois pas
de grande différence, hormis le fait de cacher le type
au compilo.




Il n'y en a pas, j'en ai juste ma claque des gnagnagna habituels sur
`ah ca je peux faire avec mon vrai langage objet'. Le truc bien avec C++,
c'est qu'en vrai, ca ne t'impose a peu pres rien sur ce que tu fais, tu
peux inventer la semantique que tu veux pour coller sur ta syntaxe.



Après, il y a un système de type, qui permet de vérifier à la compilation
que le typage est cohérent, mais qui impose de faire coincider l'intention
du développeur et le système de type du langage, et ça impose de le connaitre.

Dans une système super dynamique où le compilo ne vérifie rien,
la compilation est plus facile, et les éventuelles erreurs se chopent
à l'exécution.

(le truc pas bien, c'est que tu peux VRAIMENT faire ce que tu veux, meme
et surtout si c'est n'importe quoi).



Si c'est n'importe quoi, le système de type râle un peu quand même.
Heureusement, il y a des cast qui permettent de faire taire le
compilateur.

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
JKB
Le Thu, 26 Apr 2012 10:24:48 +0200,
Jean-Marc Bourguet écrivait :
JKB writes:

Le Thu, 26 Apr 2012 09:54:54 +0200,
Jean-Marc Bourguet écrivait :
JKB writes:

Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
l'entier 0 et le const char * NULL :-( ).



Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr). Il y a des consequences "interessantes" avec la
surcharge et les templates.



Sauf que même en forçant explicitement le type, le compilo n'en a
rien à faire. Avoir une méthode foo(const char *p) et une seconde
foo(int i) fait que foo(0) appelle la version cont char *



Etrange.

même si je
prends la peine de coller foo((int) 0).



Encore plus. Quelle version? Bon



schroedinger:[~] > gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.6.3-2'
--with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr
--program-suffix=-4.6 --enable-shared --enable-linker-build-id
--with-system-zlib --libexecdir=/usr/lib --without-included-gettext
--enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6
--libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu
--enable-libstdcxx-debug --enable-libstdcxx-time=yes
--enable-gnu-unique-object --enable-plugin --enable-objc-gc
--with-arch-32=i586 --with-tune=generic --enable-checking=release
--build=x86_64-linux-gnu --host=x86_64-linux-gnu
--target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.3 (Debian 4.6.3-2)

#include <iostream>

struct C {
void f(int) {
std::cout << "f(int)n";
}

void f(char const*) {
std::cout << "f(char const*)n";
}
};

int main()
{
C c;
c.f(0);
}

affiche f(int) avec toutes celles que j'ai sous la main et que j'ai
essayees.

Ce qui est assez amusant de
mon point de vue, c'est que foo(1) ne se pose pas la question (alors
que j'ai au moins une architecture sous la main où NULL ne vaut pas
0).



La representation physique n'a pas d'importance. Dans le code, une
constante entiere de valeur 0 est un pointeur nul qui peut physiquement
etre represente autrement qu'un int de valeur 0.



Nous sommes bien d'accord. Mais en l'occurrence, g++ se mélange les
pinceaux...

JKB
Avatar
ld
On 26 avr, 09:54, Jean-Marc Bourguet wrote:
JKB writes:
>    Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
>    savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
>    de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
>    l'entier 0 et le const char * NULL :-( ).

Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr).  Il y a des consequences "interessantes" avec l a
surcharge et les templates.

g++ donne un warning quand on utilise NULL dans des contextes non
pointeurs, mais genere du code comme si c'etait 0 de specifie.

(En C NULL peut aussi etre definit comme (void*)0, c'est pas autorise en
C++).



mais necessaire pour les appels de fonctions variadic...

a+, laurent.
Avatar
Jean-Marc Bourguet
ld writes:

(En C NULL peut aussi etre definit comme (void*)0, c'est pas autorise en
C++).



mais necessaire pour les appels de fonctions variadic...



Vu qu'en C le cast est autorise mais pas obligatoire, la situation est
la meme, en cas d'apple a une fonction variadique (ou une fonctions sans
prototype en C), l'appelant doit faire attention aux parametres.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Lucas Levrel
Le 26 avril 2012, JKB a écrit :

Sauf que même en forçant explicitement le type, le compilo n'en a
rien à faire. Avoir une méthode foo(const char *p) et une seconde
foo(int i) fait que foo(0) appelle la version cont char * même si je
prends la peine de coller foo((int) 0).
La solution pas élégante a été de rajouter une méthode pour
gérer explicitement ce cas avec un argument supplémentaire. J'avoue,
ce n'est pas propre.



Pour reprendre l'ECM de Jean-Marc Bourguet :

#include <iostream>

struct C {
void f(int) {
std::cout << "f(int)n";
}

void f(char const*) {
std::cout << "f(char const*)n";
}
};

int main()
{
C c;
c.f(0);
}



est-ce que tu ne peux pas faire :

typedef void(C::*bidouille)(int);
bidouille int_f=&C::f;

puis
(*int_f)(0);

?

C'est peut-être moins clair mais ça éviterait de définir une méthode
supplémentaire.

--
LL
Avatar
espie
In article ,
ld wrote:
On 26 avr, 09:54, Jean-Marc Bourguet wrote:
JKB writes:
>    Pour coder en C ou C++, il faut avoir une bonne hygiène de vie et
>    savoir ce que l'on fait (et avoir un compilo C++ à peu près exempt
>    de bug, j'ai trouvé un truc rigolo avec le dernier g++ entre
>    l'entier 0 et le const char * NULL :-( ).

Vu qu'en C++ NULL doit etre defini a une constante entiere de valeur 0,
je me demande si tu n'as pas trouve quelque chose de conforme, ou meme
de specifie (j'ai pas dit de desirable, C++11 definit un nullptr, pour
eviter certains problemes, mais NULL ne peut pas -- encore? -- etre
definit comme nullptr).  Il y a des consequences "interessantes" avec la
surcharge et les templates.

g++ donne un warning quand on utilise NULL dans des contextes non
pointeurs, mais genere du code comme si c'etait 0 de specifie.

(En C NULL peut aussi etre definit comme (void*)0, c'est pas autorise en
C++).



mais necessaire pour les appels de fonctions variadic...

a+, laurent.




Pour ceux qui ne l'ont pas vu, je recommande le talk d'Alexandrescu
a "Going Native 2012" sur les template variadiques, ou il arrive a refaire
un printf() totalement type-checked en C++ pur.

Pas encore eu l'occasion de pratiquer suffisamment, mais le perfect-forwarding
permis par les rvalue references (enfin, en passant par std::forward pour
faire simple) et les template variadiques, c'est totalement bluffant.

Le
template<typename ...Args>
void f(Args&& ...args)
{ g(std::forward<Args>(args)...); }

meme si c'est un truc que j'utilise sans arret en perl, je ne m'attendais
vraiment pas a le retrouver en C++ 2011.
1 2 3 4 5