¿Qué es el corte de objetos?
En C++, ¿qué es la división de objetos y cuándo ocurre?
"Cortar" es donde se asigna un objeto de una clase derivada a una instancia de una clase base, perdiendo así parte de la información; parte de ella se "corta".
Por ejemplo,
class A {
int foo;
};
class B : public A {
int bar;
};
Entonces un objeto de tipo B
tiene dos miembros de datos foo
y bar
.
Entonces si escribieras esto:
B b;
A a = b;
Entonces la información b
sobre el miembro bar
se pierde en a
.
La mayoría de las respuestas aquí no explican cuál es el problema real con el corte. Sólo explican los casos benignos de corte, no los traicioneros. Supongamos, al igual que las otras respuestas, que se trata de dos clases A
y B
, de donde B
deriva (públicamente) A
.
En esta situación, C++ le permite pasar una instancia del operador de asignación B
to A
(y también al constructor de copia). Esto funciona porque una instancia de B
se puede convertir en a const A&
, que es lo que los operadores de asignación y los constructores de copia esperan que sean sus argumentos.
El caso benigno
B b;
A a = b;
No sucede nada malo allí: solicitaste una instancia de A
la cual es una copia B
y eso es exactamente lo que obtienes. Claro, a
no contendrá algunos de b
los miembros, pero ¿cómo debería hacerlo? Después de todo, es un A
, no un B
, por lo que ni siquiera ha oído hablar de estos miembros, y mucho menos podría almacenarlos.
El caso traicionero
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
Se podría pensar que b2
será una copia de b1
después. Pero ¡ay, no lo es ! Si lo inspeccionas, descubrirás que b2
es una criatura frankensteiniana, hecha de algunos trozos de b1
(los trozos que B
hereda de A
) y algunos trozos de b2
(los trozos que solo B
contiene). ¡Ay!
¿Qué pasó? Bueno, C++ por defecto no trata a los operadores de asignación como virtual
. Por lo tanto, la línea a_ref = b1
llamará al operador de asignación de A
, no al de B
. Esto se debe a que, para funciones no virtuales, el tipo declarado (formalmente: estático ) (que es A&
) determina qué función se llama, a diferencia del tipo real (formalmente: dinámico ) (que sería B
, ya que a_ref
hace referencia a una instancia de B
) . Ahora, A
el operador de asignación de obviamente solo conoce los miembros declarados en A
, por lo que solo los copiará, dejando los miembros agregados B
sin cambios.
Una solución
Asignar sólo partes de un objeto por lo general tiene poco sentido, pero desafortunadamente C++ no proporciona ninguna manera integrada de prohibirlo. Sin embargo, puedes preparar el tuyo propio. El primer paso es hacer virtual el operador de asignación . Esto garantizará que siempre se llame al operador de asignación del tipo real , no al del tipo declarado . El segundo paso es usar dynamic_cast
para verificar que el objeto asignado tenga un tipo compatible. El tercer paso es hacer la asignación real en un miembro (¡protegido!) assign()
, ya que probablemente querrá usar 's B
para copiar los miembros de '.assign()
A
assign()
A
class A {
public:
virtual A& operator= (const A& a) {
assign(a);
return *this;
}
protected:
void assign(const A& a) {
// copy members of A from a to this
}
};
class B : public A {
public:
virtual B& operator= (const A& a) {
if (const B* b = dynamic_cast<const B*>(&a))
assign(*b);
else
throw bad_assignment();
return *this;
}
protected:
void assign(const B& b) {
A::assign(b); // Let A's assign() copy members of A from b to this
// copy members of B from b to this
}
};
Tenga en cuenta que, por pura conveniencia, B
's operator=
anula covariantemente el tipo de retorno, ya que sabe que está devolviendo una instancia de B
.
Si tiene una clase base A
y una clase derivada B
, puede hacer lo siguiente.
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
Ahora el método wantAnA
necesita una copia de derived
. Sin embargo, el objeto derived
no se puede copiar por completo, ya que la clase B
podría inventar variables miembro adicionales que no están en su clase base A
.
Por lo tanto, para llamar a wantAnA
, el compilador "cortará" todos los miembros adicionales de la clase derivada. El resultado podría ser un objeto que no deseaba crear, porque
- puede estar incompleto,
- se comporta como un objeto ( se pierde
A
todo comportamiento especial de la clase ).B
Todas estas son buenas respuestas. Solo me gustaría agregar un ejemplo de ejecución al pasar objetos por valor versus por referencia:
#include <iostream>
using namespace std;
// Base class
class A {
public:
A() {}
A(const A& a) {
cout << "'A' copy constructor" << endl;
}
virtual void run() const { cout << "I am an 'A'" << endl; }
};
// Derived class
class B: public A {
public:
B():A() {}
B(const B& a):A(a) {
cout << "'B' copy constructor" << endl;
}
virtual void run() const { cout << "I am a 'B'" << endl; }
};
void g(const A & a) {
a.run();
}
void h(const A a) {
a.run();
}
int main() {
cout << "Call by reference" << endl;
g(B());
cout << endl << "Call by copy" << endl;
h(B());
}
La salida es:
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'