Co się dzieje ?
Tworząc a Taxi, tworzysz również Carpodobiekt. A kiedy taksówka zostaje zniszczona, oba obiekty są niszczone. Kiedy dzwonisz test(), przekazujesz Carwartość. Tak więc sekunda Carzostaje 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 Carutworzony jako źródło Carargumentu. Ponieważ nie zdarza się to, gdy podaje się bezpośrednio Carwartość jako argument, podejrzewam, że służy to przekształceniu Taxiw Car. Jest to nieoczekiwane, ponieważ Carw 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
Carwartoś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 Carpodobiektu Taxi.
Więc próbowałem zadzwonić, test()ale jawnie przerzuciłem Taxina 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 Carpodobiektu Taxii 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.
Taxiobiektu do funkcji przyjmującejCarobiekt według wartości?