Alguien lo mencionó en el IRC como el problema del corte.
c++
inheritance
c++-faq
object-slicing
Frankomania
fuente
fuente
A a = b;
a
ahora es un objeto de tipoA
que tiene una copiaB::foo
. Será un error devolverlo ahora, creo.B b1; B b2; A& b2_ref = b2; b2 = b1
. Se podría pensar que haya copiadob1
ab2
, pero que no tienen! Ha copiado una parte deb1
ab2
(la parte de lab1
queB
heredó deA
), y dejó las otras partes deb2
cambios.b2
ahora es una criatura frankensteiniana que consta de unos pocos fragmentosb1
seguidos de algunos fragmentos deb2
. Ugh! Voto negativo porque creo que la respuesta es muy engañosa.B b1; B b2; A& b2_ref = b2; b2_ref = b1
" El verdadero problema se produce si usted " ... deriva de una clase con un operador de asignación no virtual. ¿A
Incluso está destinado a la derivación? No tiene funciones virtuales. Si deriva de un tipo, ¡tiene que lidiar con el hecho de que sus funciones miembro pueden ser llamadas!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, como las otras respuestas, que está tratando con dos clases
A
y deB
dóndeB
deriva (públicamente)A
.En esta situación, C ++ permite pasar una instancia de
B
aA
's operador de asignación (y también al constructor de copia). Esto funciona porque una instancia deB
se puede convertir en aconst A&
, que es lo que los operadores de asignación y los constructores de copias esperan que sean sus argumentos.El caso benigno
No sucede nada malo: solicitó una instancia de la
A
cual es una copiaB
, y eso es exactamente lo que obtiene. Claro,a
no contendrá algunos deb
los miembros de, pero ¿cómo debería? Es unA
, después de todo, no unB
, por lo que ni siquiera ha oído hablar de estos miembros, y mucho menos podría almacenarlos.El traicionero caso
Puede pensar que
b2
será una copia deb1
después. Pero, por desgracia, es no ! Si lo inspeccionas, descubrirás queb2
es una criatura frankensteiniana, hecha de algunos trozos deb1
(los trozos queB
hereda deA
), y algunos trozos deb2
(los trozos que soloB
contiene). ¡Ay!¿Que pasó? Bueno, C ++ por defecto no trata a los operadores de asignación como
virtual
. Por lo tanto, la líneaa_ref = b1
llamará al operador de asignación deA
, no al deB
. Esto se debe a que, para funciones no virtuales, el tipo declarado (formalmente: estático ) (que esA&
) determina qué función se llama, en oposición al tipo real (formalmente: dinámico ) (que seríaB
, ya que hacea_ref
referencia a una instancia deB
) . Ahora,A
el operador de asignación obviamente conoce solo los miembros declaradosA
, por lo que copiará solo esos, dejando los miembros agregadosB
sin cambios.Una solución
Asignar solo a partes de un objeto generalmente tiene poco sentido, pero C ++, desafortunadamente, no proporciona una forma integrada de prohibir esto. Sin embargo, puedes rodar el tuyo. 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 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 (protegida!)assign()
, Ya queB
'sassign()
probablemente va a querer usarA
Esassign()
copiarA
's, miembros.Tenga en cuenta que, por pura conveniencia,
B
'soperator=
anula covariantemente el tipo de retorno, ya que sabe que está devolviendo una instancia deB
.fuente
derived
se puede dar cualquier valor al código que espera unbase
valor, o se puede usar cualquier referencia derivada como referencia base. Me gustaría ver un lenguaje con un sistema de tipos que aborde ambos conceptos por separado. Hay muchos casos en los que una referencia derivada debe ser sustituible por una referencia base, pero las instancias derivadas no deben ser sustituibles por las base; También hay muchos casos en los que las instancias deberían ser convertibles, pero las referencias no deberían sustituir.Si tiene una clase base
A
y una clase derivadaB
, puede hacer lo siguiente.Ahora el método
wantAnA
necesita una copia dederived
. Sin embargo, el objetoderived
no se puede copiar completamente, ya que la claseB
podría inventar variables miembro adicionales que no están en su clase baseA
.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 desea crear, porqueA
(B
se pierde todo el comportamiento especial de la clase ).fuente
wantAnA
(como su nombre lo indica) quiere unA
, entonces eso es lo que obtiene. Y una instancia deA
, se comportará como unA
. ¿Cómo es eso sorprendente?derived
al tipoA
. La conversión implícita siempre es una fuente de comportamiento inesperado en C ++, porque a menudo es difícil de entender al mirar el código localmente que se realizó una conversión.Estas son todas buenas respuestas. Solo me gustaría agregar un ejemplo de ejecución al pasar objetos por valor vs por referencia:
El resultado es:
fuente
La tercera coincidencia en google para "C ++ slicing" 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 informarnos qué más está buscando.
fuente
El problema de 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 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 derivadas 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 la basura.
fuente
Derived
es convertible implícitamente aBase
). Esto obviamente es contrario al Principio Abierto-Cerrado, y una gran carga de mantenimiento.En C ++, un objeto de clase derivada puede asignarse a un objeto de clase base, pero a la inversa no es posible.
La división de objetos ocurre cuando un objeto de clase derivada se asigna a un objeto de clase base, los atributos adicionales de un objeto de clase derivada se cortan para formar el objeto de clase base.
fuente
El problema de corte en C ++ surge de la semántica de valor de sus objetos, que se mantuvo principalmente debido a la compatibilidad con estructuras de C. Debe usar una referencia explícita o una sintaxis de puntero para lograr un comportamiento de objeto "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 son 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 de valor, el corte es un comportamiento razonable y tiene usos relativamente raros, que no existen en la mayoría de los otros idiomas. Algunas personas lo consideran una característica de C ++, mientras que muchos lo consideran una de las peculiaridades / errores de C ++.
fuente
struct
, compatibilidad u otra falta de sentido que cualquier sacerdote aleatorio de OOP te dijo.Base
debe tomar exactamentesizeof(Base)
bytes en la memoria, con una posible alineación, tal vez, es por eso que "asignación" (copia en pila ) no copiará a los miembros de clase derivados, sus desplazamientos están fuera de sizeof. Para evitar la "pérdida de datos", simplemente use el puntero, como cualquier otra persona, ya que la memoria del puntero está fija en su lugar y tamaño, mientras que la pila es muy volátilEntonces ... ¿Por qué es malo perder la información derivada? ... porque el autor de la clase derivada puede haber cambiado la representación de modo 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 a la representación base.
También pensé que alguien debería mencionar lo que debe hacer para evitar cortar ... Obtenga una copia de los estándares de codificación de C ++, 101 pautas de reglas y las mejores prácticas. Tratar con rebanar es el # 54.
Sugiere un patrón algo sofisticado para tratar completamente el problema: tener un constructor de copia protegida, 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 explícita, lo que permite un corte explícito si se desea.
fuente
1. LA DEFINICIÓN DEL PROBLEMA DE CORTE
Si D es una clase derivada de la clase base B, puede asignar un objeto de tipo Derivado a una variable (o parámetro) de tipo Base.
EJEMPLO
Aunque se permite la asignación anterior, el valor que se asigna a la mascota variable pierde su campo de raza. Esto se llama el problema de corte .
2. CÓMO ARREGLAR EL PROBLEMA DE CORTE
Para vencer el problema, utilizamos punteros a variables dinámicas.
EJEMPLO
En este caso, ninguno de los miembros de datos o funciones de miembro de la variable dinámica a la que apunta ptrD (objeto de clase descendiente) se perderá. Además, si necesita usar funciones, la función debe ser una función virtual.
fuente
dog
eso no sea parte de la clasePet
(elbreed
miembro de datos) no se copie en la variablepet
? El código solo está interesado en losPet
miembros de datos, aparentemente. Rebanar es definitivamente un "problema" si no es deseado, pero no lo veo aquí.((Dog *)ptrP)
" Sugiero usarstatic_cast<Dog*>(ptrP)
Dog::breed
) no es un ERROR relacionado con SLICING?Me parece que cortar en rodajas no es tanto un problema que no sea 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 tenerlo en cuenta y conocerlo internamente, el método llamado solo funcionará con el objeto de superclase (también conocido como clase base).
Me parece solo la expectativa irracional de que proporcionar una subclase donde se solicita una clase base, de alguna manera resultaría en resultados específicos de la 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 generalmente es el resultado de sacrificar un buen diseño de OOP en favor de la conveniencia o las ganancias de rendimiento.
fuente
OK, lo intentaré después de leer muchas publicaciones que explican la división de objetos, pero no cómo se vuelve problemático.
El escenario vicioso que puede provocar daños en la memoria es el siguiente:
fuente
La división 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:
Como las funciones de copia de clase base no saben nada sobre el derivado, solo se copia la parte base del derivado. Esto se conoce comúnmente como rebanar.
fuente
fuente
cuando un objeto de clase derivada se asigna a un objeto de clase base, los atributos adicionales de un objeto de clase derivada se cortan (descartan) del objeto de clase base.
fuente
Cuando un objeto de clase derivada se asigna a un objeto de clase base, todos los miembros del objeto de clase derivada se copian en un objeto de clase base, excepto los miembros que no están presentes en la clase base. Estos miembros son cortados por el compilador. Esto se llama segmentación de objetos.
Aquí hay un ejemplo:
Generará:
fuente
Acabo de encontrarme con el problema del corte y rápidamente llegué aquí. Así que déjame agregar mis dos centavos a esto.
Veamos un ejemplo del "código de producción" (o algo parecido):
Digamos que tenemos algo que despacha acciones. Una interfaz de usuario del centro de control, por ejemplo.
Esta interfaz de usuario debe obtener una lista de las cosas que se pueden enviar actualmente. Entonces definimos una clase que contiene la información de despacho. Digamos que es
Action
. Entonces anAction
tiene algunas variables miembro. Por simplicidad solo tenemos 2, siendo astd::string name
y astd::function<void()> f
. Luego tiene unvoid activate()
que simplemente ejecuta alf
miembro.Entonces la interfaz de usuario obtiene un
std::vector<Action>
suministro. Imagine algunas funciones como:Ahora hemos establecido cómo se ve desde la perspectiva de la interfaz de usuario. No hay problema hasta ahora. Pero otro tipo que trabaja en este proyecto de repente decide que hay acciones especializadas que necesitan más información en el
Action
objeto. Por qué razón alguna vez. Eso también podría resolverse con capturas lambda. Este ejemplo no se toma 1-1 del código.Entonces el chico deriva de
Action
agregar su propio sabor.Él pasa una instancia de su clase casera a la
push_back
pero luego el programa se vuelve loco.¿Entonces qué pasó?
Como usted podría haber adivinado: el objeto ha sido cortada.
La información adicional de la instancia se ha perdido y
f
ahora es propensa a comportamientos indefinidos.Espero que este ejemplo arroje luz para aquellas personas que realmente no pueden imaginarse las cosas cuando hablan de que
A
s yB
s se derivan de alguna manera.fuente