Co się dzieje ?
Tworząc a Taxi
, tworzysz również Car
podobiekt. A kiedy taksówka zostaje zniszczona, oba obiekty są niszczone. Kiedy dzwonisz test()
, przekazujesz Car
wartość. Tak więc sekunda Car
zostaje skonstruowana jako kopia i zostanie zniszczona, gdy test()
zostanie pozostawiona. Mamy więc wyjaśnienie dla 3 niszczycieli: pierwszego i dwóch ostatnich w sekwencji.
Czwarty destruktor (czyli drugi w sekwencji) jest nieoczekiwany i nie mogłem odtworzyć z innymi kompilatorami.
Może być tylko tymczasowo Car
utworzony jako źródło Car
argumentu. Ponieważ nie zdarza się to, gdy podaje się bezpośrednio Car
wartość jako argument, podejrzewam, że służy to przekształceniu Taxi
w Car
. Jest to nieoczekiwane, ponieważ Car
w każdym jest już podobiekt Taxi
. Dlatego myślę, że kompilator dokonuje niepotrzebnej konwersji na temp i nie wykonuje kopiowania, które mogłoby uniknąć tej temp.
Wyjaśnienie podane w komentarzach:
Oto wyjaśnienie w odniesieniu do standardu dla prawnika ds. Języka w celu weryfikacji moich roszczeń:
- Konwersja, o której tu mówię, jest konwersją konstruktora
[class.conv.ctor]
, tj. Konstruowaniem obiektu jednej klasy (tutaj Car) na podstawie argumentu innego typu (tutaj Taxi).
- Ta konwersja wykorzystuje wówczas obiekt tymczasowy do zwrócenia swojej
Car
wartości. Kompilator byłby uprawniony do wykonania korekcji kopiowania zgodnie z tym [class.copy.elision]/1.1
, że zamiast konstruować tymczasowy, mógłby skonstruować wartość, która zostanie zwrócona bezpośrednio do parametru.
- Więc jeśli ta temperatura daje efekty uboczne, to dlatego, że kompilator najwyraźniej nie korzysta z tej możliwości eliminacji kopii. To nie jest złe, ponieważ wybór kopii nie jest obowiązkowy.
Eksperymentalne potwierdzenie analizy
Mogę teraz odtworzyć Twój przypadek za pomocą tego samego kompilatora i narysować eksperyment, aby potwierdzić, co się dzieje.
Moje powyższe założenie było takie, że kompilator wybrał proces nieoptymalnego przekazywania parametrów, używając konwersji konstruktora Car(const &Taxi)
zamiast konstruowania kopii bezpośrednio z Car
podobiektu Taxi
.
Więc próbowałem zadzwonić, test()
ale jawnie przerzuciłem Taxi
na Car
.
Mojej pierwszej próbie nie udało się poprawić sytuacji. Kompilator nadal używał nieoptymalnej konwersji konstruktora:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
Moja druga próba się powiodła. Wykonuje także rzutowanie, ale używa rzutowania wskaźnika, aby zdecydowanie zasugerować kompilatorowi użycie Car
podobiektu Taxi
i bez tworzenia tego głupiego obiektu tymczasowego:
test(*static_cast<Car*>(&taxi)); // :-)
I niespodzianka: działa zgodnie z oczekiwaniami, produkując tylko 3 wiadomości o zniszczeniu :-)
Podsumowujący eksperyment:
W ostatnim eksperymencie podałem niestandardowego konstruktora poprzez konwersję:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
i zaimplementuj to za pomocą *this = *static_cast<Car*>(&taxi);
. Brzmi głupio, ale generuje również kod, który wyświetla tylko 3 komunikaty destruktora, unikając w ten sposób niepotrzebnego obiektu tymczasowego.
Prowadzi to do wniosku, że w kompilatorze może być błąd, który powoduje takie zachowanie. Chodzi o to, że w niektórych okolicznościach można by pominąć możliwość bezpośredniego tworzenia kopii z klasy podstawowej.
Taxi
obiektu do funkcji przyjmującejCar
obiekt według wartości?