Qué está pasando ?
Cuando crea un Taxi
, también crea un Car
subobjeto. Y cuando el taxi se destruye, ambos objetos se destruyen. Cuando llamas test()
pasas el Car
por valor. Entonces, un segundo Car
se construye con copia y se destruirá cuando test()
se deje. Entonces tenemos una explicación para 3 destructores: el primero y los dos últimos en la secuencia.
El cuarto destructor (que es el segundo en la secuencia) es inesperado y no pude reproducirlo con otros compiladores.
Solo puede ser un temporal Car
creado como fuente para el Car
argumento. Como no sucede cuando se proporciona un Car
valor directamente como argumento, sospecho que es para transformarlo Taxi
en Car
. Esto es inesperado, ya que ya hay un Car
subobjeto en cada uno Taxi
. Por lo tanto, creo que el compilador realiza una conversión innecesaria en una temperatura y no realiza la copia de elisión que podría haber evitado esta temperatura.
Aclaración dada en los comentarios:
Aquí la aclaración con referencia a la norma para el lenguaje-abogado para verificar mis reclamos:
- La conversión a la que me refiero aquí, es una conversión por constructor
[class.conv.ctor]
, es decir, construir un objeto de una clase (aquí Car) basado en un argumento de otro tipo (aquí Taxi).
- Esta conversión utiliza entonces un objeto temporal para devolver su
Car
valor. Se le permitiría al compilador hacer una copia de la elisión de acuerdo con esto [class.copy.elision]/1.1
, ya que en lugar de construir un temporal, podría construir el valor que se devolverá directamente en el parámetro.
- Entonces, si esta temperatura produce efectos secundarios, es porque el compilador aparentemente no hace uso de esta posible copia de elisión. No está mal, ya que la elisión de copia no es obligatoria.
Confirmación experimental del análisis.
Ahora podría reproducir su caso utilizando el mismo compilador y dibujar un experimento para confirmar lo que está sucediendo.
Mi suposición anterior fue que el compilador seleccionó un proceso de paso de parámetros subóptimos, utilizando la conversión de constructor en Car(const &Taxi)
lugar de la construcción de copia directamente desde el Car
subobjeto de Taxi
.
Así que traté de llamar test()
pero explícitamente lancé el Taxi
a Car
.
Mi primer intento no logró mejorar la situación. El compilador aún usaba la conversión de constructor subóptima:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
Mi segundo intento tuvo éxito. También realiza el vaciado, pero usa el vaciado del puntero para sugerir encarecidamente al compilador que use el Car
subobjeto del Taxi
y sin crear este tonto objeto temporal:
test(*static_cast<Car*>(&taxi)); // :-)
Y sorpresa: funciona como se esperaba, produciendo solo 3 mensajes de destrucción :-)
Experimento final:
En un experimento final, proporcioné un constructor personalizado por conversión:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
e implementarlo con *this = *static_cast<Car*>(&taxi);
. Suena tonto, pero esto también genera código que solo mostrará 3 mensajes destructores, evitando así el objeto temporal innecesario.
Esto lleva a pensar que podría haber un error en el compilador que causa este comportamiento. Es posible que, en algunas circunstancias, se pierda la posibilidad de construir copias directamente desde la clase base.
Taxi
objeto a una función que toma unCar
objeto por valor?