Що відбувається ?
Коли ви створюєте Taxi
, ви також створюєте Car
субопредмет. А коли таксі знищується, обидва об’єкти руйнуються. Під час дзвінка test()
ви передаєте Car
значення за значенням. Таким чином, секунда Car
отримує копію і буде знищена, коли test()
її залишиться. Отже, у нас є пояснення для 3 деструкторів: перший і два останні в послідовності.
Четвертий деструктор (тобто другий у послідовності) несподіваний, і я не зміг відтворити інші компілятори.
Він може бути лише тимчасовим, Car
створеним як джерело Car
аргументу. Оскільки це не відбувається при наданні прямого Car
значення в якості аргументу, я підозрюю, що це для перетворення Taxi
в Car
. Це несподівано, оскільки Car
у кожного вже є субектив Taxi
. Тому я думаю, що компілятор робить непотрібне перетворення в темп і не робить елісій копіювання, який міг би уникнути цієї температури.
Пояснення, дане в коментарях:
Ось роз'яснення з посиланням на стандарт для мови-юриста для перевірки моїх претензій:
- Конверсія, про яку я тут маю на увазі, - це перетворення конструктором
[class.conv.ctor]
, тобто побудова об'єкта одного класу (тут Car) на основі аргументу іншого типу (тут Taxi).
- Ця конверсія використовує тоді тимчасовий об'єкт для повернення свого
Car
значення. Компілятору було б дозволено зробити елісію копії відповідно до того [class.copy.elision]/1.1
, що замість побудови тимчасової вона може побудувати значення, яке буде повернуто безпосередньо в параметр.
- Отже, якщо ця темп дає побічні ефекти, це тому, що компілятор, очевидно, не використовує цю можливу копію-елізію. Це не неправильно, оскільки копіювати елізію не обов’язково.
Експериментальне підтвердження аналізу
Тепер я міг відтворити ваш випадок, використовуючи той самий компілятор і провести експеримент, щоб підтвердити, що відбувається.
Моє припущення вище полягало в тому, що компілятор обрав процес передачі неоптимальних параметрів, використовуючи перетворення конструктора Car(const &Taxi)
замість побудови копії безпосередньо з Car
субекта Taxi
.
Тож я спробував зателефонувати, test()
але явно перекинув Taxi
на a Car
.
Першої моєї спроби покращити ситуацію не вдалося. Компілятор все ще використовував неоптимальне перетворення конструктора:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
Друга моя спроба вдалася. Він також виконує кастинг, але використовує кастинг покажчиків, щоб настійно запропонувати компілятору використовувати Car
субект Taxi
і без створення цього нерозумного тимчасового об'єкта:
test(*static_cast<Car*>(&taxi)); // :-)
І сюрприз: він працює як слід, створюючи лише 3 повідомлення про знищення :-)
Завершальний експеримент:
В кінцевому експерименті я надав спеціальний конструктор шляхом перетворення:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
і реалізувати це за допомогою *this = *static_cast<Car*>(&taxi);
. Звучить нерозумно, але це також генерує код, який відображатиме лише 3 повідомлення про деструктори, тим самим уникаючи зайвого тимчасового об’єкта.
Це призводить до думки, що в компіляторі може виникнути помилка, яка викликає таку поведінку. Це - можливість прямої копіювання з базового класу була б упущена за певних обставин.
Taxi
об'єкта функції, що приймаєCar
об'єкт за значенням?