Что происходит?
Когда вы создаете Taxi
, вы также создаете подобъект Car
. А когда такси уничтожается, оба объекта разрушаются. Когда вы звоните test()
, вы передаете Car
по значению. Таким образом, вторая Car
создается копией и будет разрушена, когда test()
останется. Итак, у нас есть объяснение 3 деструкторам: первый и два последних в последовательности.
Четвертый деструктор (второй в последовательности) неожиданный, и я не смог воспроизвести его с другими компиляторами.
Это может быть только временный Car
, созданный в качестве источника для аргумента Car
. Так как этого не происходит при прямом предоставлении значения Car
в качестве аргумента, я подозреваю, что это для преобразования Taxi
в Car
. Это неожиданно, поскольку в каждом Taxi
уже есть подобъект Car
. Поэтому я думаю, что компилятор делает ненужное преобразование в temp и не выполняет копирование, которое могло бы избежать этой температуры.
Разъяснения даны в комментариях:
Вот пояснения со ссылкой на стандарт для языка-юриста для проверки моих претензий:
- Преобразование, на которое я здесь ссылаюсь, - это преобразование по конструктору
[class.conv.ctor]
, т.е. создание объекта одного класса (здесь Car) на основе аргумента другого типа (здесь Taxi). - Это преобразование затем использует временный объект для возврата значения
Car
. Компилятору будет разрешено создавать копию в соответствии с [class.copy.elision]/1.1
, поскольку вместо создания временного он может создать значение, которое будет возвращено непосредственно в параметр. - Так что, если этот temp дает побочные эффекты,это потому, что компилятор явно не использует это возможное копирование. Это не так, поскольку копирование не обязательно.
Экспериментальное подтверждение анализа
Теперь я могу воспроизвести ваш случай, используя тот же компилятор, и провести эксперимент, чтобы подтвердить происходящее.
Мое вышеизложенное предположение заключалось в том, что компилятор выбрал неоптимальный процесс передачи параметров, используя преобразование конструктора Car(const &Taxi)
вместо копирования непосредственно из подобъекта Car
объекта Taxi
.
Поэтому я попытался позвонить test()
, но явно преобразовал Taxi
в 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 сообщения деструктора, таким образом избегая ненужного временного объекта.
Это наводит на мысль, что в компиляторе может быть ошибка, вызывающая такое поведение. Это означает, что при некоторых обстоятельствах возможность прямого конструирования копии из базового класса будет упущена.