Qu'est-ce qui se passe ?
Lorsque vous créez un Taxi, vous créez également un Carsous - objet. Et lorsque le taxi est détruit, les deux objets sont détruits. Lorsque vous appelez, test()vous passez la Carvaleur par. Donc une seconde Carest construite à partir d' une copie et sera détruite quand elle test()sera laissée. Nous avons donc une explication pour 3 destructeurs: le premier et les deux derniers de la séquence.
Le quatrième destructeur (c'est le deuxième de la séquence) est inattendu et je n'ai pas pu reproduire avec d'autres compilateurs.
Il ne peut s'agir que d'un temporaire Carcréé comme source pour l' Carargument. Comme cela ne se produit pas lorsque vous fournissez directement une Carvaleur en argument, je soupçonne que c'est pour transformer le Taxien Car. C'est inattendu, car il y a déjà un Carsous - objet dans chaque Taxi. Par conséquent, je pense que le compilateur effectue une conversion inutile en temp et ne fait pas l'élision de copie qui aurait pu éviter ce temp.
Précision donnée dans les commentaires:
Voici la clarification en référence à la norme pour la langue-avocat pour vérifier mes réclamations:
- La conversion dont je parle ici, est une conversion par constructeur
[class.conv.ctor], c'est-à-dire la construction d'un objet d'une classe (ici Car) sur la base d'un argument d'un autre type (ici Taxi).
- Cette conversion utilise ensuite un objet temporaire pour renvoyer sa
Carvaleur. Le compilateur serait autorisé à effectuer une élision de copie selon [class.copy.elision]/1.1, car au lieu de construire un temporaire, il pourrait construire la valeur à renvoyer directement dans le paramètre.
- Donc, si ce temp donne des effets secondaires, c'est parce que le compilateur ne fait apparemment pas usage de cette possible élision de copie. Ce n'est pas faux, car la suppression de la copie n'est pas obligatoire.
Confirmation expérimentale de l'anaysis
Je pourrais maintenant reproduire votre cas en utilisant le même compilateur et dessiner une expérience pour confirmer ce qui se passe.
Mon hypothèse ci-dessus était que le compilateur a sélectionné un processus de passage de paramètres sous-optimal, en utilisant la conversion du constructeur Car(const &Taxi)au lieu de copier la construction directement à partir du Carsous - objet de Taxi.
J'ai donc essayé d'appeler test()mais de lancer explicitement le Taxidans un Car.
Ma première tentative n'a pas réussi à améliorer la situation. Le compilateur utilisait toujours la conversion du constructeur sous-optimal:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
Ma deuxième tentative a réussi. Il effectue également la conversion, mais utilise la conversion de pointeur afin de suggérer fortement au compilateur d'utiliser le Carsous - objet de Taxiet sans créer cet objet temporaire idiot:
test(*static_cast<Car*>(&taxi)); // :-)
Et surprise: cela fonctionne comme prévu, ne produisant que 3 messages de destruction :-)
Expérience finale:
Dans une dernière expérience, j'ai fourni un constructeur personnalisé par conversion:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
et l'implémenter avec *this = *static_cast<Car*>(&taxi);. Cela semble idiot, mais cela génère également du code qui n'affichera que 3 messages de destructeur, évitant ainsi l'objet temporaire inutile.
Cela conduit à penser qu'il pourrait y avoir un bogue dans le compilateur qui provoque ce comportement. C'est af est la possibilité de construction de copie directe à partir de la classe de base serait manquée dans certaines circonstances.
Taxiobjet à une fonction prenant unCarobjet par valeur?