2009-07-05 16 views
5

Supongamos que Y es una clase derivada de la clase X y X declara que foo es virtual. Supongamos que y es de tipo (Y *). Entonces ((X *) y) -> foo() ejecutará la versión Y de foo(), pero ((X) * y) .foo() ejecutará la versión X. ¿Puede decirme por qué el polimorfismo no se aplica en el caso desreferenciado? Yo esperaría que cualquiera de las sintaxis arroje la versión Y de foo().polimorfismo C++ ((X *) y) -> foo() vs ((X) * y) .foo()

Respuesta

0

Creo que eso se debe simplemente a la forma en que se especifica el idioma. Las referencias y punteros usan enlaces finales siempre que sea posible, mientras que los objetos usan enlaces iniciales. Sería posible hacer un enlace tardío en cada caso (me imagino), pero un compilador que lo hizo no estaría siguiendo las especificaciones de C++.

8

Un molde siempre (*) crea un nuevo objeto del tipo al que está creando, que se construye utilizando el objeto que está emitiendo.

Fundir en X * crea un nuevo puntero (es decir, un objeto de tipo X *). Tiene el mismo valor que y, por lo que sigue apuntando al mismo objeto, de tipo Y.

Fundir en X crea una nueva X. Se construye usando *y, pero de lo contrario no tiene nada que ver con el objeto viejo . En su ejemplo, se llama al foo() en este nuevo objeto "temporal", no en el objeto apuntado por y.

Tiene la razón de que el polimorfismo dinámico solo se aplica a punteros y referencias, no a objetos, y esta es la razón por la cual: si tiene un puntero a X, entonces lo que señala podría ser una subclase de X. Pero si tienes una X, entonces es una X, y nada más. las llamadas virtuales serían inútiles.

(*) a menos que la optimización permita la omisión del código que no cambia el resultado. Pero la optimización no puede cambiar lo que se llama función foo().

+1

... y ((X &) * y) .foo() _hace_ llamar al foo derivado, porque al lanzar una Y & a una X & no se crea una nueva X. – Doug

+0

Sí, probablemente debería decir "siempre crea un objeto nuevo del tipo en el que estás creando contenido, a menos que el tipo al que estás enviando no sea del tipo de objeto". Casting para el tipo de referencia "crea" una nueva referencia vinculada al objeto original. –

0

creo que la explicación de Darth Eru es correcta, y aquí es por eso que pienso C++ se comporta de esa manera:

El código (X) * y es como crear una variable local que es de tipo X. El compilador necesita asigne sizeof (X) espacio en la pila, y descarta cualquier dato adicional incluido en un objeto de tipo Y, de modo que cuando llame a foo() tenga que ejecutar la versión X. Sería difícil para el compilador comportarse de una manera que le permita llamar a la versión Y.

El código (X *) y es como crear un puntero a un objeto, y el compilador sabe que el objeto apuntado es X o una subclase de X. En tiempo de ejecución cuando desreferencia el puntero y llama a foo con "- > foo() "se determina la clase del objeto y se usa la función adecuada.

+0

Recuerde que (X) * y se define como el mismo que X (* y), es decir, una llamada a un constructor de X para crear un nuevo objeto X. No sería simplemente difícil, ni siquiera es deseable que un objeto X tenga la versión Y de foo() invocada, simplemente porque se construyó utilizando un objeto Y. –

2

La eliminación de referencias (la parte *y) está muy bien, pero el reparto (la parte (X)) crea un nuevo objeto (temporal) específicamente de la clase X - eso es lo que significa la fundición . Entonces, el objeto tiene que tener la tabla virtual de la clase X - considere que la conversión habrá eliminado los miembros de instancia agregados por Y en la creación de subclases (de hecho, ¿cómo es posible que el copiador de X los conozca?), Entonces Sería potencialmente un desastre si se ejecutara alguna de las anulaciones de Y, asegurándose de que this apunta a una instancia de Y, completa con miembros agregados y todo ... ¡cuando ese conocimiento fuera falso!

La versión en la que lanzas punteros es, por supuesto, completamente diferente - la *X ha llegado a los mismos bits como el Y*, por lo que todavía está apuntando a una instancia perfectamente válida de Y (de hecho, se está señalando y, por supuesto)

La triste realidad es que, por seguridad, el copiador de una clase solo debería ser invocado con, como argumento, una instancia de esa clase, no de ninguna subclase; la pérdida de miembros de instancia agregados & c es demasiado perjudicial. Pero la única manera de garantizar eso es siguiendo el excelente consejo de Haahr, "No subclases clases concretas" ... aunque está escribiendo sobre Java, el consejo es al menos tan bueno para C++ (que tiene esta copia del código) rebanando "problema además! -)

+0

Si la pérdida de los miembros extra de Y es dañina cuando se copia X, entonces la clase Y no satisface el principio de sustitución de Liskov, o es más probable que el llamador no se haya dado cuenta de que está lanzando (porción no intencionada) y cree que todavía tiene un objeto de la clase Y. De cualquier manera, no creo que el error sea, como tal, llamar al constructor de copia con una Y. No está apreciando que el resultado sea una X construida a partir de la Y como cualquier otro constructor de 1-arg y no algo polimorfo –

+0

El corte involuntario es la causa más probable, y si no subclases las clases concretas (además de las otras razones da Haahr) ¡ese es un tipo de accidente que no ocurrirá! -) –

+0

Claro, es una regla sólida de pulgar. También estoy de acuerdo con Haahr en que hay casos en los que vale la pena arriesgar tu brazo y subclasificar, porque a veces la herencia es realmente útil. Pero en realidad debe ser posible satisfacer a Liskov, que es donde la mayoría de las subclases de clases concretas salen mal. Si, como dice Haahr, * cualquier aspecto * de la superclase se "arrastra involuntariamente", entonces ya ha fallado. Pero puedes subclasificar clases concretas, en los raros casos tiene sentido. Si es así, el copiador también tiene sentido, y aquellos que entienden qué cortarlo lo evitarán de todos modos, al no hacerlo. –

10

Estás cortando la parte del objeto Y y copia el objeto en un objeto X. La función invocada se llama a un objeto X y, por lo tanto, se llama a la función X.

Cuando especifica un tipo en C++ en una declaración o conversión, eso significa que el objeto declarado o casted-to es en realidad de ese tipo, no de un tipo derivado.

Si usted quiere tratar simplemente el objeto está siendo del tipo X (es decir, si desea que el tipo estático de la expresión sea X, pero aún así quieren que denotan un objeto Y) y luego lanzas a una tipo de referencia

((X&)*y).foo() 

Esta llamará a la función en el objeto Y, y no cortar ni copiar en un objeto X. En los pasos, esto hace

  • Desreferencia el puntero y, que es de tipo Y*. La desreferenciación produce una expresión lvalue del tipo Y. Una expresión lvalue realmente puede denotar un objeto de un tipo derivado, incluso si su tipo estático es el de su base.
  • Se envía a X&, que es una referencia a X. Eso producirá una expresión lvalue de tipo X.
  • Llamar a la función.

su reparto original hicieron

  • Desreferencia el puntero y.
  • La expresión resultante se fundió en X. Esto producirá una operación de copia en un nuevo objeto X. La expresión resultante de eso es una expresión rvalue del tipo estático X. El tipo dinámico del objeto denotado es tambiénX, al igual que con todas las expresiones rvalue.
  • Llamar a la función.
Cuestiones relacionadas