Pregunta ¿Qué es el corte de objetos?


Alguien lo mencionó en el IRC, pero Google no tiene una buena respuesta.


608
2017-11-08 11:10


origen


Respuestas:


"Cortar" es donde asigna un objeto de una clase derivada a una instancia de una clase base, perdiendo así parte de la información, parte de la cual 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 tuviera que escribir esto:

B b;

A a = b;

Entonces la información en b sobre miembro bar se pierde en a.


517
2017-11-08 11:22



La mayoría de las respuestas aquí no explican cuál es el problema real con el corte. Solo explican los benignos casos de rebanar, no los traicioneros. Asume, como las otras respuestas, que estás tratando con dos clases A y B, dónde B deriva (públicamente) de A.

En esta situación, C ++ le permite pasar una instancia de B a Aoperador de asignaciones (y también al constructor de copias). Esto funciona porque una instancia de B se puede convertir 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 pasa nada malo allí; pediste una instancia de A que es una copia de B, y eso es exactamente lo que obtienes. Por supuesto, a no contendrá algunos de bmiembros de S, pero ¿cómo debería? Se trata de un ADespués de todo, no es una B, entonces ni siquiera tiene oído acerca de estos miembros, y mucho menos podrían almacenarlos.

El caso traicionero

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Podrías pensar que b2 será una copia de b1 después. Pero, por desgracia, es no! Si lo inspecciona, descubrirá que b2 es una criatura Frankensteiniana, hecha de algunos trozos de b1 (los pedazos que B hereda de A), y algunos trozos de b2 (los pedazos que solo B contiene). ¡Ay!

¿Que 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 el de B. Esto se debe a que para las funciones no virtuales, el declarado tipo (que es A&) determina qué función se llama, a diferencia de la real tipo (que sería B, ya que a_ref hace referencia a una instancia de B) Ahora, AEl operador de asignación obviamente solo conoce a los miembros declarados en A, por lo que copiará solo eso, dejando a los miembros agregados en B sin alterar.

Una solución

Asignar solo partes de un objeto generalmente tiene poco sentido, sin embargo, desafortunadamente, C ++ no proporciona una forma incorporada de prohibir esto. Sin embargo, puedes hacer tu propio. El primer paso es hacer que el operador de asignación virtual. Esto garantizará que siempre sea el real tipo de operador de asignación que se llama, no el declarado tipo de El segundo paso es usar dynamic_cast para verificar que el objeto asignado tiene un tipo compatible. El tercer paso es hacer la asignación real en un miembro (protegido) assign(), ya que Bes assign() probablemente querrá usar Aes assign() copiar Amiembros de.

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, Bes operator= de forma coherente anula el tipo de devolución, ya que sabe que está devolviendo una instancia de B.


396
2018-01-22 15:00



Si tienes una clase base A y una clase derivada B, entonces puedes 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 wantAnA, el compilador "cortará" todos los miembros adicionales de la clase derivada. El resultado puede ser un objeto que no deseaba crear, porque

  • puede estar incompleto,
  • se comporta como un A-objeto (todo el comportamiento especial de la clase B está perdido).

134
2017-11-08 11:28



El tercer partido en google para "cortar en C ++" me da este artículo de Wikipedia http://en.wikipedia.org/wiki/Object_slicing y esto (calentado, pero las primeras publicaciones definen el problema): http://bytes.com/forum/thread163565.html

Entonces es cuando asigna un objeto de una subclase a la súper clase. La superclase no sabe nada de la información adicional en la subclase y no tiene espacio para almacenarla, por lo que la información adicional se "corta".

Si esos enlaces no brindan suficiente información para una "buena respuesta", edite su pregunta y háganos saber qué más está buscando.


30
2017-11-08 11:14



Estas son todas buenas respuestas. Me gustaría agregar un ejemplo de ejecución al pasar objetos por valor vs 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());
}

El resultado es:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

28
2017-08-22 18:33



El problema de corte es grave porque puede provocar daños en la memoria y es muy difícil garantizar que un programa no lo sufra. Para diseñarlo fuera del lenguaje, las clases que admiten herencia deben ser accesibles solo por referencia (no por valor). El lenguaje de programación D tiene esta propiedad.

Considere la clase A, y la clase B derivada de A. La corrupción de la memoria puede ocurrir si la parte A tiene un puntero p, y una instancia B que apunta a los datos adicionales de la p. Luego, cuando los datos adicionales se cortan, p apunta a la basura.


24
2017-11-08 11:56



El problema de corte en C ++ surge de la semántica de valores de sus objetos, que se mantuvo principalmente debido a la compatibilidad con las estructuras de C. Debe usar referencia explícita o sintaxis de puntero para lograr el comportamiento de objetos "normal" que se encuentra en la mayoría de los otros lenguajes que hacen objetos, es decir, los objetos siempre se pasan por referencia.

Las respuestas cortas consisten en cortar el objeto asignando un objeto derivado a un objeto base por valor, es decir, el objeto restante es solo una parte del objeto derivado. Para preservar la semántica de valores, cortar es un comportamiento razonable y tiene usos relativamente raros, que no existe en la mayoría de los otros lenguajes. Algunas personas lo consideran una característica de C ++, mientras que muchos lo consideran una de las peculiaridades / características erróneas de C ++.


8
2017-11-09 00:31



1. LA DEFINICIÓN DEL PROBLEMA DE REBANADO

Si D es una clase derivada de la clase base B, entonces puede asignar un objeto de tipo Derivado a una variable (o parámetro) de tipo Base.

EJEMPLO

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

Aunque la asignación anterior está permitida, el valor asignado a la mascota variable pierde su campo de raza. Esto se llama el problema de corte.

2. CÓMO ARREGLO EL PROBLEMA DE REBANADO

Para vencer el problema, usamos punteros a variables dinámicas.

EJEMPLO

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

En este caso, ninguno de los miembros de datos o funciones miembro de la variable dinámica ser apuntado por ptrD (objeto de clase descendiente) se perderá. Además, si necesita usar funciones, la función debe ser una función virtual.


8
2018-01-28 18:00



Entonces ... ¿Por qué es malo perder la información derivada? ... porque el autor de la clase derivada puede haber cambiado la representación de tal manera que dividir la información adicional cambie el valor representado por el objeto. Esto puede suceder si la clase derivada se usa para almacenar en caché una representación que sea más eficiente para ciertas operaciones, pero costosa para volver a la representación base.

También pensé que alguien también debería mencionar lo que debes hacer para evitar rebanar ... Obtenga una copia de los Estándares de codificación de C ++, las pautas de 101 reglas y las mejores prácticas. Tratar con rebanar es # 54.

Sugiere un patrón algo sofisticado para tratar completamente el problema: tener un constructor de copia protegida, una DoClone virtual pura protegida y una clonación pública con una afirmación que le dirá si una clase derivada (posterior) no pudo implementar DoClone correctamente. (El método Clone realiza una copia profunda adecuada del objeto polimórfico).

También puede marcar el constructor de copia en la base explícita, lo que permite un corte explícito si así lo desea.


6
2017-11-08 17:38