Une dernière étape pour lier tout ça, le RTTI :
Vous pouvez utiliser RTTI pour gérer correctement les fonctions virtuelles qui prennent en compte votre type. Voici la dernière pièce du puzzle pour savoir comment gérer correctement les affectations lorsqu'il s'agit de types éventuellement hérités.
virtual B& operator=(const B& right)
{
const D *pD = dynamic_cast<const D*>(&right);
if(pD)
{
x = pD->x;
y = pD->y;
}
else
{
x = right.x;
y = 13;//default value
}
return *this;
}
Je voudrais ajouter quelques remarques à cette solution. Le fait que l'opérateur d'affectation soit déclaré de la même manière que ci-dessus pose trois problèmes.
Le compilateur génère un opérateur d'affectation qui prend un fichier const D& qui n'est pas virtuel et ne fait pas ce que vous pensez qu'il fait.
Le deuxième problème est le type de retour, vous renvoyez une référence de base à une instance dérivée. Ce n'est probablement pas un gros problème puisque le code fonctionne de toute façon. Cependant, il est préférable de retourner les références en conséquence.
Troisièmement, l'opérateur d'assignation de type dérivé n'appelle pas l'opérateur d'assignation de la classe de base (que se passe-t-il s'il y a des champs privés que vous voulez copier ?), déclarer l'opérateur d'assignation comme virtuel ne fera pas en sorte que le compilateur en génère un pour vous. C'est plutôt un effet secondaire de ne pas avoir au moins deux surcharges de l'opérateur d'assignation pour obtenir le résultat souhaité.
Considérant la classe de base (la même que celle du post que j'ai cité) :
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
Le code suivant complète la solution RTTI que j'ai citée :
class D : public B{
public:
// The virtual keyword is optional here because this
// method has already been declared virtual in B class
/* virtual */ const D& operator =(const B& b){
// Copy fields for base class
B::operator =(b);
try{
const D& d = dynamic_cast<const D&>(b);
// Copy D fields
y = d.y;
}
catch (std::bad_cast){
// Set default values or do nothing
}
return *this;
}
// Overload the assignment operator
// It is required to have the virtual keyword because
// you are defining a new method. Even if other methods
// with the same name are declared virtual it doesn't
// make this one virtual.
virtual const D& operator =(const D& d){
// Copy fields from B
B::operator =(d);
// Copy D fields
y = d.y;
return *this;
}
int y;
};
Cela peut sembler une solution complète, mais ce n'est pas le cas. Ce n'est pas une solution complète car lorsque vous dérivez de D, vous aurez besoin d'un opérateur = qui prend const B& , 1 opérateur = qui prend const D& et un opérateur qui prend const D2& . La conclusion est évidente, le nombre de surcharges de l'opérateur =() est équivalent au nombre de super classes + 1.
Étant donné que D2 hérite de D, regardons à quoi ressemblent les deux méthodes de l'opérateur =() héritées.
class D2 : public D{
/* virtual */ const D2& operator =(const B& b){
D::operator =(b); // Maybe it's a D instance referenced by a B reference.
try{
const D2& d2 = dynamic_cast<const D2&>(b);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
/* virtual */ const D2& operator =(const D& d){
D::operator =(d);
try{
const D2& d2 = dynamic_cast<const D2&>(d);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
};
Il est évident que le opérateur =(const D2&) il suffit de copier les champs, d'imaginer comme si c'était là. Nous pouvons remarquer un modèle dans les surcharges de l'opérateur hérité =(). Malheureusement, il n'est pas possible de définir des méthodes de template virtuelles qui prendraient en charge ce modèle, nous devons copier et coller plusieurs fois le même code afin d'obtenir un opérateur d'assignation polymorphe complet, la seule solution que je vois. Cela s'applique également aux autres opérateurs binaires.
Modifier
Comme mentionné dans les commentaires, le moins que l'on puisse faire pour faciliter la vie est de définir l'opérateur d'affectation de superclasse le plus élevé =(), et de l'appeler à partir de toutes les autres méthodes d'opérateur de superclasse =(). De même, lors de la copie de champs, une méthode _copy peut être définie.
class B{
public:
// _copy() not required for base class
virtual const B& operator =(const B& b){
x = b.x;
return *this;
}
int x;
};
// Copy method usage
class D1 : public B{
private:
void _copy(const D1& d1){
y = d1.y;
}
public:
/* virtual */ const D1& operator =(const B& b){
B::operator =(b);
try{
_copy(dynamic_cast<const D1&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing.
}
return *this;
}
virtual const D1& operator =(const D1& d1){
B::operator =(d1);
_copy(d1);
return *this;
}
int y;
};
class D2 : public D1{
private:
void _copy(const D2& d2){
z = d2.z;
}
public:
// Top-most superclass operator = definition
/* virtual */ const D2& operator =(const B& b){
D1::operator =(b);
try{
_copy(dynamic_cast<const D2&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
// Same body for other superclass arguments
/* virtual */ const D2& operator =(const D1& d1){
// Conversion to superclass reference
// should not throw exception.
// Call base operator() overload.
return D2::operator =(dynamic_cast<const B&>(d1));
}
// The current class operator =()
virtual const D2& operator =(const D2& d2){
D1::operator =(d2);
_copy(d2);
return *this;
}
int z;
};
Il n'y a pas besoin d'un définir les valeurs par défaut car elle ne recevrait qu'un seul appel (dans la surcharge de l'opérateur de base =()). Les modifications apportées lors de la copie des champs sont effectuées à un seul endroit et toutes les surcharges de l'opérateur =() sont concernées et remplissent leur fonction.
Merci sehe pour la suggestion.