O que está acontecendo ?
Ao criar um Taxi
, você também cria um Car
subobjeto. E quando o táxi é destruído, os dois objetos são destruídos. Quando você liga, test()
passa o Car
valor por. Assim, um segundo Car
é copiado e será destruído quando test()
for deixado. Portanto, temos uma explicação para três destruidores: o primeiro e os dois últimos na sequência.
O quarto destruidor (que é o segundo na sequência) é inesperado e não consegui reproduzir com outros compiladores.
Só pode ser temporário Car
criado como fonte para o Car
argumento. Como isso não acontece ao fornecer diretamente um Car
valor como argumento, suspeito que seja para transformar o Taxi
em Car
. Isso é inesperado, pois já existe um Car
subobjeto em todos Taxi
. Portanto, acho que o compilador faz uma conversão desnecessária em um temp e não faz a cópia elision que poderia ter evitado esse temp.
Esclarecimentos dados nos comentários:
Aqui o esclarecimento com referência ao padrão para o advogado de idiomas para verificar minhas reivindicações:
- A conversão a que me refiro aqui é uma conversão por construtor
[class.conv.ctor]
, ou seja, a construção de um objeto de uma classe (aqui Carro) com base em um argumento de outro tipo (aqui Táxi).
- Essa conversão usa um objeto temporário para retornar seu
Car
valor. O compilador poderia fazer uma cópia elisão de acordo [class.copy.elision]/1.1
, pois, em vez de construir um temporário, ele poderia construir o valor a ser retornado diretamente no parâmetro.
- Portanto, se esta temperatura fornecer efeitos colaterais, é porque o compilador aparentemente não faz uso dessa possível cópia elisão. Não está errado, uma vez que a remoção de cópias não é obrigatória.
Confirmação experimental da análise
Agora eu poderia reproduzir seu caso usando o mesmo compilador e desenhar um experimento para confirmar o que está acontecendo.
Minha suposição acima foi que o compilador selecionou um processo de passagem de parâmetro abaixo do ideal, usando a conversão do construtor em Car(const &Taxi)
vez de copiar a construção diretamente do Car
subobjeto de Taxi
.
Então, eu tentei ligar, test()
mas explicitamente Taxi
converter o em a Car
.
Minha primeira tentativa não conseguiu melhorar a situação. O compilador ainda usou a conversão de construtor abaixo do ideal:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
Minha segunda tentativa foi bem sucedida. Ele também executa a conversão, mas usa a conversão de ponteiro para sugerir fortemente que o compilador use o Car
subobjeto de Taxi
e sem criar este objeto temporário bobo:
test(*static_cast<Car*>(&taxi)); // :-)
E surpresa: funciona como esperado, produzindo apenas 3 mensagens de destruição :-)
Experiência final:
Em uma experiência final, forneci um construtor personalizado por conversão:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
e implementá-lo com *this = *static_cast<Car*>(&taxi);
. Parece bobo, mas isso também gera código que exibirá apenas três mensagens destruidoras, evitando assim o objeto temporário desnecessário.
Isso leva a pensar que poderia haver um erro no compilador que causa esse comportamento. É uma possibilidade de que a construção direta de cópias da classe base seja perdida em algumas circunstâncias.
Taxi
objeto para uma função que leva umCar
objeto por valor?