Qu'est-ce qui se passe ?
Lorsque vous créez un Taxi
, vous créez également un Car
sous - objet. Et lorsque le taxi est détruit, les deux objets sont détruits. Lorsque vous appelez, test()
vous passez la Car
valeur par. Donc une seconde Car
est 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 Car
créé comme source pour l' Car
argument. Comme cela ne se produit pas lorsque vous fournissez directement une Car
valeur en argument, je soupçonne que c'est pour transformer le Taxi
en Car
. C'est inattendu, car il y a déjà un Car
sous - 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
Car
valeur. 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 Car
sous - objet de Taxi
.
J'ai donc essayé d'appeler test()
mais de lancer explicitement le Taxi
dans 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 Car
sous - objet de Taxi
et 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.
Taxi
objet à une fonction prenant unCar
objet par valeur?