On Apr 30, 2:59 am, Mickaël Wolff <mickael.wo...@laposte.net> wrote:
> Si on a les trois classes suivantes :
> class A
> {
> public:
> virtual A & operator=(A const & rvalue) ;
> /* ... */
> } ;
> class B0 : public A
> {
> public:
> virtual A & operator=(B0 const & rvalue) ;
> /* ... */
> } ;
> class B1 : public A
> {
> public:
> virtual A & operator=(B1 const & rvalue) ;
> /* ... */
> } ;
> Dans l'usage suivant :
> B0 b0 ;
> B1 b1 ;
> A & b0_a = static_cast<A &>(b0) ;
> b1 = b0_a ;
> Dans la pratique, comment devrais-je gérer une telle
> affectation ?
Dans la pratique, tu dois te démander si ça a un sens. Au moins
dans les applications que je fais, le polymorphisme s'applique
surtout à des objets ayant une identité, et donc, qui ne
supporte ni l'affectation ni la copie.
Sinon, il faut que tu définisses ce que ça signifie dans des cas
comme ceci.
Considère, par exemple, ton code. D'abord, virtuel ou non, tes
opérateurs d'affectation dans les classes dérivées ne
supplantent pas la fonction virtuelle dans la classe de base,
parce qu'ils n'ont pas les mêmes paramètres. Pour qu'il y a
supplantage, il faut définir un opérateur avec la même signature
dans les classes dérivées, c-à-d :
class B0 : public A {
{
public:
virtual B0& operator=( A const& other ) ;
// ...
} ;
(Que l'opérateur renvoie un B0& ou un A&, en revanche, n'a pas
d'importance, puisque le C++ supporte les types de retour
co-variants.)
Du coup, ton code ne passe pas le compilateur, parce que
l'opérateur d'affectation de b1 exige bien un B1 (l'opérateur
d'affectation dans B1 cache bien celui dans A), et tu lui passe
un A. (Pour les paramètres, il n'y a que le type statique qui
compte.)
> Lever une exception car c'est un non sens ?
Est-ce un non sens, en voilà la question ? Dans les rares cas
où l'affectation d'un type polymorphique aient du sens, ce n'est
pas forcement un non sens. Encore, tout dépend. Je vois tout de
suite trois possibilités :
1. On n'accepte l'affectation qu'entre des types dynamiques
identiques. Mais dans ce cas-là, je me démande si on veut
réelement l'affectation (ou la polymorphisme) ; pour
certaines opérations (l'affectation), on n'est pas du tout
polymorphique, et pour d'autres si. C'est en tout cas une
violation flagrante du LSP.
S'il le faudrait, j'interdirais l'affectation au niveau de
la classe de base, et ne l'implémenterait qu'au niveau des
classes dérivées. Si le client veut affecter, il faut qu'il
sache avoir des instances de la même dérivée, et qu'il
utilise leur interface. (Par exemple, qu'il ait fait un
dynamic_cast avant, pour traiter un cas spécial.)
Dans la pratique, ce cas arrive facilement quand la classe
de base représente une qualité plutôt indépendante du rôle
de la classe, quelque chose comme PersistentObject. Dans ce
cas-là, en revanche, on ne traite avec la classe de base que
dans les fonctions qui gèrent cette qualité, ici par
exemple, les fonctions d'entrée/sortie. Et l'affectation se
fera bien toujours avec des classes dérivées.
2. Les classes dérivées représentent une famille, avec un état
abstrait commun. (Je ne peux malheureusement pas penser à de
bons exemples ; je crois que le cas ne s'est jamais
présenté dans mes applications.) Dans ce cas-là, il s'agit
d'écrire un opérateur d'affectation qui extrait l'état
commun, quelque soit le type à droit. Dans le cas le plus
général, il faudrait le « double dispatch », que la
fonction réelement appelée dépend à la fois du type de
l'objet à gauche, et de celui à droit.
Dans ce cas-ci, je ferais plutôt une fonction virtualle
assign() dans la classe de base, avec des surcharges pour
toutes les classes dérivées (implémentation classique du
double dispatch), ou qui cherche la fonction à appeler dans
un map (autre implémentation du double dispatch) ;
l'opérateur d'affectation se contentera d'appeler cette
fonction. Ou les opérateurs d'affectation, plutôt ; il en
faudrait au moins deux dans chaque classe dérivée -- un de
copie, pour empêcher que le compilateur en génère le sien,
et un qui prend une référence à la classe de base, pour
pouvoir affecter les d'autres types dérivés.
3. Enfin, on a réelement à faire avec une valeur polymorphique,
dont le type dynamique fasse partie de la valeur, et doit
être changé lors de l'affectation. Dans ce cas-ci, il faut
l'idiome du lettre/enveloppe. C-à-d que le type de base
contient un pointeur à l'objet réelement polymorphique, et y
renvoie tous les opérations. Et que tous les objets déclarés
soit du type de base, et que l'affectation fasse un clone de
l'objet contenu par l'objet à droit. Ça marche pas mal, mais
c'est un peu lourd, avec un clonage à chaque affectation ou
copie. (Dans le cas où les objets sont immutable, en dehors
de l'affectation, on peut éviter le clonage en se servant
d'un glaneur de cellules, ou éventuellement d'un comptage de
références. Ce qui le rend réelement utilisable, dans
beaucoup de cas.)
> Et surtout, comment je le détecte dans l'affectation ?
La détection, c'est le problème la plus simple :
if ( typeid( *this ) != typeid( other ) ) ...
Savoir ce qu'il faut faire (c-à-d la conception) qui est plus
difficile.
> La solution la plus directe que je vois dans le cas où on gère
> est un dynamic_cast dans B0:
perator=(A const &). J'avoue que
> je serais partisan de rendre l'opération silencieuse, mais je
> risque de me surprendre, en perdant des information lors
> d'affectations.
> Que faites-vous pratiquement dans ce cas ?
Je les évite, autant que possible. Dans la pratique, je trouve
qu'ils apparaissent assez rarement, et quand ils apparaissent,
ou bien, je me trouve dans le cas 1, ci-dessus, mais celui qui
veut affecter sait toujours très bien le type (suite à un
dynamic_cast au retour de la lecture, par exemple), et le
problème ne se pose pas réelement, ou je me trouve dans le cas
3, mais avec des objets immutable, et je m'en tire en simplement
affectant un pointeur (intelligent, genre boost::shared_ptr, si
je n'ai pas de glaneur de cellules).
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34