¿Qué es el corte de objetos?

Resuelto Frankomania asked hace 15 años • 0 respuestas

En C++, ¿qué es la división de objetos y cuándo ocurre?

Frankomania avatar Nov 08 '08 18:11 Frankomania
Aceptado

"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 Btiene dos miembros de datos fooy bar.

Entonces si escribieras esto:

B b;

A a = b;

Entonces la información bsobre el miembro barse pierde en a.

David Dibben avatar Nov 08 '2008 11:11 David Dibben

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 Ay B, de donde Bderiva (públicamente) A.

En esta situación, C++ le permite pasar una instancia del operador de asignación Bto A(y también al constructor de copia). Esto funciona porque una instancia de Bse 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 Ala cual es una copia By eso es exactamente lo que obtienes. Claro, ano contendrá algunos de blos 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 b2será una copia de b1después. Pero ¡ay, no lo es ! Si lo inspeccionas, descubrirás que b2es una criatura frankensteiniana, hecha de algunos trozos de b1(los trozos que Bhereda de A) y algunos trozos de b2(los trozos que solo Bcontiene). ¡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 = b1llamará 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_refhace referencia a una instancia de B) . Ahora, Ael operador de asignación de obviamente solo conoce los miembros declarados en A, por lo que solo los copiará, dejando los miembros agregados Bsin 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_castpara 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 Bpara copiar los miembros de '.assign()Aassign()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.

fgp avatar Jan 22 '2013 15:01 fgp

Si tiene una clase base Ay 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 wantAnAnecesita una copia de derived. Sin embargo, el objeto derivedno se puede copiar por completo, ya que la clase Bpodrí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 Atodo comportamiento especial de la clase ).B
Black avatar Nov 08 '2008 11:11 Black

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'
geh avatar Aug 22 '2014 18:08 geh