jerarquia - operadores logicos en c++
operador de asignaciĆ³n virtual C++ (6)
escribió:
Un último paso para unir todo, RTTI:
Puede usar RTTI para manejar adecuadamente las funciones virtuales que se adaptan a su tipo. Aquí está la última pieza del rompecabezas para descubrir cómo manejar adecuadamente la asignación cuando se trata de tipos posiblemente heredados.
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; }
Me gustaría agregar a esta solución algunas observaciones. Tener el operador de asignación declarado como el anterior tiene tres problemas.
El compilador genera un operador de asignación que toma una const D & argumento que no es virtual y no hace lo que crees que hace.
El segundo problema es el tipo de devolución, está devolviendo una referencia base a una instancia derivada. Probablemente no sea un problema ya que el código funciona de todos modos. Aún así, es mejor devolver las referencias en consecuencia.
Tercero, el operador de asignación de tipo derivado no llama al operador de asignación de clase base (¿qué pasa si hay campos privados que le gustaría copiar?), Declarar el operador de asignación como virtual no hará que el compilador genere uno para usted. Esto es más bien un efecto secundario de no tener al menos dos sobrecargas del operador de asignación para obtener el resultado deseado.
Teniendo en cuenta la clase base (igual que la de la publicación que cité):
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
El siguiente código completa la solución RTTI que cité:
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;
};
Esto puede parecer una solución completa, no lo es. Esta no es una solución completa porque cuando se deriva de D, necesitará 1 operador = que toma const B & , 1 operador = que toma const D & y un operador que toma const D2 & . La conclusión es obvia, el número de sobrecargas operador = () es equivalente con el número de super clases + 1.
Teniendo en cuenta que D2 hereda D, echemos un vistazo a cómo son los dos métodos heredados operator = ().
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;
}
};
Es obvio que el operador = (const D2 &) solo copia los campos, imagina como si estuviera allí. Podemos observar un patrón en las sobrecargas heredadas del operador = (). Lamentablemente no podemos definir los métodos de plantillas virtuales que se encargarán de este patrón, tenemos que copiar y pegar varias veces el mismo código para obtener un operador de asignación polimórfica completo, la única solución que veo. También se aplica a otros operadores binarios.
Editar
Como se mencionó en los comentarios, lo mínimo que se puede hacer para facilitar la vida es definir el operador de asignación de superclase superior = (), y llamarlo desde todos los demás operadores de superclase = (). Además, al copiar campos, se puede definir un método _copy.
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;
};
No hay necesidad de un método de configuración predeterminada porque solo recibiría una llamada (en la operación base = () sobrecarga). Los cambios al copiar campos se realizan en un solo lugar y todas las sobrecargas del operador = () se ven afectadas y tienen el propósito previsto.
Gracias sehe por la sugerencia.
Asignación El operador en C ++ se puede hacer virtual. ¿Por qué es requerido? ¿Podemos hacer que otros operadores sean virtuales también?
Depende del operador.
El objetivo de virtualizar un operador de asignación es permitirle el beneficio de poder anularlo para copiar más campos.
Entonces, si tiene una Base & y en realidad tiene un tipo Derivado y como un tipo dinámico, y el Derivado tiene más campos, las cosas correctas se copian.
Sin embargo, existe el riesgo de que su LHS sea un Derivado, y el RHS sea una Base, de modo que cuando el operador virtual se ejecuta en Derivado, su parámetro no es un Derivado y usted no tiene forma de obtener campos fuera de él.
Aquí hay una buena discusión: http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html
Se requiere solo cuando desea garantizar que las clases derivadas de su clase obtengan todos sus miembros copiados correctamente. Si no estás haciendo nada con polimorfismo, entonces realmente no necesitas preocuparte por esto.
No conozco nada que evite que virtualice cualquier operador que desee; no son más que llamadas a métodos especiales.
Esta página proporciona una descripción excelente y detallada de cómo funciona todo esto.
Un operador es un método con una sintaxis especial. Puedes tratarlo como cualquier otro método ...
la asignación virtual se usa en los escenarios siguientes:
//code snippet
Class Base;
Class Child :public Base;
Child obj1 , obj2;
Base *ptr1 , *ptr2;
ptr1= &obj1;
ptr2= &obj2 ;
//Virtual Function prototypes:
Base& operator=(const Base& obj);
Child& operator=(const Child& obj);
caso 1: obj1 = obj2;
En este concepto virtual no juega ningún papel ya que llamamos operator=
en Child
clase Child
.
caso 2 y 3: * ptr1 = obj2;
* ptr1 = * ptr2;
Aquí la asignación no será como se esperaba. Motivo siendo operator=
se llama en la clase Base
lugar.
Se puede rectificar usando:
1) Casting
dynamic_cast<Child&>(*ptr1) = obj2; // *(dynamic_cast<Child*>(ptr1))=obj2;`
dynamic_cast<Child&>(*ptr1) = dynamic_cast<Child&>(*ptr2)`
2) Concepto virtual
Ahora, simplemente usando virtual Base& operator=(const Base& obj)
no ayudará, ya que las firmas son diferentes en Child
y Base
para operator=
.
Necesitamos agregar Base& operator=(const Base& obj)
en la clase Child junto con su definición de Child& operator=(const Child& obj)
. Es importante incluir una definición posterior, ya que en ausencia de ese operador de asignación predeterminado se llamará. ( obj1=obj2
podría no dar el resultado deseado)
Base& operator=(const Base& obj)
{
return operator=(dynamic_cast<Child&>(const_cast<Base&>(obj)));
}
caso 4: obj1 = * ptr2;
En este caso, el compilador busca la definición operator=(Base& obj)
en Child
como operator=
se llama en Child. Pero como no está presente y el tipo Base
no puede ser promovido a child
implícitamente, lo hará a través de un error. (Se requiere obj1=dynamic_cast<Child&>(*ptr1);
como obj1=dynamic_cast<Child&>(*ptr1);
)
Si implementamos de acuerdo con los casos 2 y 3, este escenario será resuelto.
Como se puede ver, la asignación virtual hace que la llamada sea más elegante en el caso de asignaciones que utilizan punteros / referencia de clase Base.
¿Podemos hacer que otros operadores sean virtuales también? Sí
No se requiere que el operador de asignación se haga virtual.
La discusión a continuación es sobre operator=
, pero también se aplica a cualquier sobrecarga del operador que tome en el tipo en cuestión, y cualquier función que tome en el tipo en cuestión.
La discusión a continuación muestra que la palabra clave virtual no conoce la herencia de un parámetro con respecto a encontrar una firma de función coincidente. En el ejemplo final, muestra cómo manejar adecuadamente la asignación cuando se trata de tipos heredados.
Las funciones virtuales no conocen la herencia del parámetro:
La firma de una función debe ser la misma para que la virtual entre en juego. Entonces, aunque en el siguiente ejemplo, operator = se hace virtual, la llamada nunca actuará como una función virtual en D, porque los parámetros y el valor de retorno de operator = son diferentes.
La función B::operator=(const B& right)
y D::operator=(const D& right)
son 100% completamente diferentes y se ven como 2 funciones distintas.
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
int y;
};
Valores predeterminados y tener 2 operadores sobrecargados:
Aunque puede definir una función virtual que le permita establecer valores predeterminados para D cuando se asigna a la variable de tipo B. Esto es incluso si su variable B es realmente una D almacenada en una referencia de una B. No obtendrá la D::operator=(const D& right)
función.
En el caso siguiente, una asignación de 2 objetos D almacenados dentro de 2 referencias B ... se usa la anulación D::operator=(const B& right)
.
//Use same B as above
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
virtual B& operator=(const B& right)
{
x = right.x;
y = 13;//Default value
return *this;
}
int y;
};
int main(int argc, char **argv)
{
D d1;
B &b1 = d1;
d1.x = 99;
d1.y = 100;
printf("d1.x d1.y %i %i/n", d1.x, d1.y);
D d2;
B &b2 = d2;
b2 = b1;
printf("d2.x d2.y %i %i/n", d2.x, d2.y);
return 0;
}
Huellas dactilares:
d1.x d1.y 99 100
d2.x d2.y 99 13
Lo que muestra que D::operator=(const D& right)
nunca se usa.
Sin la palabra clave virtual en B::operator=(const B& right)
tendría los mismos resultados que arriba, pero el valor de y no se inicializaría. Es decir, usaría B::operator=(const B& right)
Un último paso para unir todo, RTTI:
Puede usar RTTI para manejar adecuadamente las funciones virtuales que se adaptan a su tipo. Aquí está la última pieza del rompecabezas para descubrir cómo manejar adecuadamente la asignación cuando se trata de tipos posiblemente heredados.
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;
}