¿Qué es rebanar objetos?

Nov 08 2008

Alguien lo mencionó en el IRC como el problema del corte.

Respuestas

635 DavidDibben Nov 08 2008 at 18:22

"Rebanar" 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 tuvieras que escribir esto:

B b;

A a = b;

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

530 fgp Jan 22 2013 at 22:00

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

En esta situación, C ++ le permite pasar una instancia del operador de asignación de 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í: solicitó una instancia de la Acual es una copia B, y eso es exactamente lo que obtiene. Seguro, ano contendrá algunos de blos miembros, pero ¿cómo debería hacerlo? Es una A, después de todo, no una B, por lo que ni siquiera ha oído acerca de estos miembros, y mucho menos sería capaz de 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 b2será una copia de b1después. Pero, por desgracia, es no ! Si lo inspecciona, descubrirá que b2es una criatura de Frankenstein, hecha de algunos trozos de b1(los trozos de los que Bhereda A) y algunos trozos de b2(los trozos que solo Bcontiene). ¡Ay!

¿Qué sucedió? 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 las 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 hace a_refreferencia a una instancia de B) . Ahora, Ael operador de asignación de, obviamente, solo conoce los miembros declarados A, por lo que copiará solo esos, dejando los miembros agregados Bsin cambios.

Una solución

La asignación solo a partes de un objeto generalmente tiene poco sentido, pero C ++, desafortunadamente, no proporciona una forma incorporada de prohibir esto. Sin embargo, puede enrollar el suyo. El primer paso es hacer que el operador de asignación sea virtual . Esto garantizará que siempre se llame al operador de asignación del tipo real , no al tipo declarado . El segundo paso es utilizar dynamic_castpara 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 B's assign()probablemente querrá usar A' s assign()para copiar A's, miembros.

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 covariablemente el tipo de retorno, ya que sabe que está devolviendo una instancia de B.

158 Black Nov 08 2008 at 18:28

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 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 A-objeto ( Bse pierde todo comportamiento especial de la clase ).
42 geh Aug 23 2014 at 01:33

Todas estas son buenas respuestas. Solo 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());
}

La salida es:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'
30 TheArchetypalPaul Nov 08 2008 at 18:14

La tercera coincidencia en Google para "segmentación de C ++" me da este artículo de Wikipedia. http://en.wikipedia.org/wiki/Object_slicing y esto (acalorado, pero las primeras publicaciones definen el problema): http://bytes.com/forum/thread163565.html

Entonces es cuando asigna un objeto de una subclase a la superclase. 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 para hacernos saber qué más está buscando.

29 WalterBright Nov 08 2008 at 18:56

El problema de la segmentación 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 la 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 p a los datos adicionales de B. Luego, cuando los datos adicionales se cortan, p apunta a basura.

11 KartikMaheshwari Mar 07 2018 at 16:35

En C ++, un objeto de clase derivada se puede asignar a un objeto de clase base, pero no es posible al revés.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

La división de objetos ocurre cuando se asigna un objeto de clase derivada a un objeto de clase base, los atributos adicionales de un objeto de clase derivada se cortan para formar el objeto de clase base.

7 SteveSteiner Nov 09 2008 at 00:38

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 cortar la información adicional cambia el valor que representa el objeto. Esto puede suceder si la clase derivada se usa para almacenar en caché una representación que es más eficiente para ciertas operaciones, pero costosa de transformar de nuevo a la representación base.

También pensé que alguien también debería mencionar lo que debe hacer para evitar cortar ... Obtenga una copia de los estándares de codificación C ++, las directrices de 101 reglas y las mejores prácticas. Lidiar con el corte es el # 54.

Sugiere un patrón algo sofisticado para abordar completamente el problema: tenga un constructor de copia protegido, un DoClone virtual puro protegido y un Clone público con una afirmación que le dirá si una clase derivada (adicional) no pudo implementar DoClone correctamente. (El método Clone hace una copia profunda adecuada del objeto polimórfico).

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

7 ididak Nov 09 2008 at 07:31

El problema de la división en C ++ surge de la semántica de valor de sus objetos, que se mantuvo principalmente debido a la compatibilidad con las estructuras de C. Necesita usar una referencia explícita o sintaxis de puntero para lograr el comportamiento "normal" del objeto que se encuentra en la mayoría de los otros lenguajes que utilizan objetos, es decir, los objetos siempre se pasan por referencia.

La respuesta corta es que corta 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 del valor, la segmentación es un comportamiento razonable y tiene usos relativamente raros, que no existen 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 / fallas de C ++.

6 haberdar Jan 29 2012 at 01:00

1. LA DEFINICIÓN DE PROBLEMA DE REBANADO

Si D es una clase derivada de la clase base B, entonces puede asignar un objeto de tipo Derived 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 se permite la asignación anterior, el valor que se asigna a la variable mascota pierde su campo de raza. A esto se le llama el problema del corte .

2. CÓMO SOLUCIONAR EL PROBLEMA DEL 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, no se perderá ninguno de los miembros de datos o funciones miembro de la variable dinámica a la que apunta ptrD (objeto de clase descendiente). Además, si necesita utilizar funciones, la función debe ser una función virtual.

4 Minok Jul 25 2009 at 02:45

Me parece que la división no es un problema tanto más que cuando sus propias clases y programas están mal diseñados / diseñados.

Si paso un objeto de subclase como parámetro a un método, que toma un parámetro de tipo superclase, ciertamente debería ser consciente de eso y saber internamente, el método llamado funcionará solo con el objeto de superclase (también conocido como clase base).

Me parece que solo la expectativa irrazonable de que proporcionar una subclase donde se solicita una clase base, de alguna manera daría como resultado resultados específicos de subclase, causaría que el corte sea un problema. Es un diseño deficiente en el uso del método o una implementación de subclase deficiente. Supongo que suele ser el resultado de sacrificar un buen diseño de programación orientada a objetos en favor de la conveniencia o la mejora del rendimiento.

3 Dude Oct 18 2012 at 10:22

Bien, lo intentaré después de leer muchas publicaciones que explican el corte de objetos, pero no cómo se vuelve problemático.

El escenario vicioso que puede resultar en la corrupción de la memoria es el siguiente:

  • La clase proporciona asignación (accidentalmente, posiblemente generada por el compilador) en una clase base polimórfica.
  • El cliente copia y corta una instancia de una clase derivada.
  • El cliente llama a una función de miembro virtual que accede al estado cortado.
3 Santosh Mar 13 2014 at 01:08

Cortar significa que los datos agregados por una subclase se descartan cuando un objeto de la subclase se pasa o se devuelve por valor o desde una función que espera un objeto de clase base.

Explicación: considere la siguiente declaración de clase:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

Como las funciones de copia de la clase base no saben nada sobre el derivado, solo se copia la parte base del derivado. Esto se conoce comúnmente como rebanar.

1 quidkid Nov 29 2012 at 19:32
class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}
1 Sorush Sep 12 2020 at 18:27

Veo que todas las respuestas mencionan cuando la división de objetos ocurre cuando se cortan los miembros de datos. Aquí doy un ejemplo de que los métodos no se anulan:

class A{
public:
    virtual void Say(){
        std::cout<<"I am A"<<std::endl;
    }
};

class B: public A{
public:
    void Say() override{
        std::cout<<"I am B"<<std::endl;
    }
};

int main(){
   B b;
   A a1;
   A a2=b;

   b.Say(); // I am B
   a1.Say(); // I am A
   a2.Say(); // I am A   why???
}

B (objeto b) se deriva de A (objeto a1 y a2). by a1, como esperamos, llaman a su función miembro. Pero desde el punto de vista del polimorfismo, no esperamos que a2, que es asignado por b, no sea anulado. Básicamente, a2 solo guarda una parte de clase A de b y eso es la división de objetos en C ++.

Para resolver este problema, se debe utilizar una referencia o un puntero.

 A& a2=b;
 a2.Say(); // I am B

o

A* a2 = &b;
a2->Say(); // I am B

Para más detalles, vea mi publicación.