#include <iostream>
using namespace std;
class Car
{
public:
~Car() { cout << "Car is destructed." << endl; }
};
class Taxi :public Car
{
public:
~Taxi() {cout << "Taxi is destructed." << endl; }
};
void test(Car c) {}
int main()
{
Taxi taxi;
test(taxi);
return 0;
}
это вывод :
Car is destructed.
Car is destructed.
Taxi is destructed.
Car is destructed.
Я использую MS Visual Studio Community 2017 (извините, я не знаю, как увидеть издание Visual C ++). Когда я использовал режим отладки. Я считаю, что один деструктор выполняется при выходе из void test(Car c){ }
тела функции, как и ожидалось. И дополнительный деструктор появился, когда test(taxi);
закончился.
test(Car c)
Функция использует значение в качестве формального параметра. Автомобиль копируется при переходе на функцию. Поэтому я думал, что при выходе из функции будет только один «Автомобиль разрушен». Но на самом деле есть два «Автомобиль разрушен» при выходе из функции (первая и вторая строки, как показано в выходных данных) Почему есть два «Автомобиль разрушен»? Спасибо.
===============
когда я добавляю виртуальную функцию, class Car
например: virtual void drive() {}
Тогда я получаю ожидаемый результат.
Car is destructed.
Taxi is destructed.
Car is destructed.
Taxi
объекта в функцию, принимающуюCar
объект по значению?Car
этого, эта проблема исчезнет и даст ожидаемые результаты.Ответы:
Похоже, что компилятор Visual Studio немного сокращает
taxi
время вызова функции для вызова функции, что по иронии судьбы приводит к тому, что он выполняет больше работы, чем можно было ожидать.Во-первых, он берет ваше
taxi
и копирует конструированиеCar
из него, так что аргумент совпадает.Тогда, это копирование
Car
снова для передачи по значению.Это поведение исчезает, когда вы добавляете пользовательский конструктор копирования, поэтому компилятор, кажется, делает это по своим собственным причинам (возможно, внутренне, это более простой путь кода), используя тот факт, что он «разрешен», потому что Само копирование тривиально. Тот факт, что вы все еще можете наблюдать это поведение, используя нетривиальный деструктор, является некоторой аберрацией.
Я не знаю, насколько это допустимо (особенно после C ++ 17), или почему компилятор выбрал бы этот подход, но я согласен, что это не тот результат, который я ожидал бы интуитивно ожидать. Ни GCC, ни Clang не делают этого, хотя может случиться так, что они делают то же самое, но тогда они лучше удаляют копию. У меня есть заметил , что даже VS 2019 все еще не велико на гарантированной элизии.
источник
Что происходит ?
Когда вы создаете
Taxi
, вы также создаетеCar
подобъект. А когда такси уничтожается, оба объекта разрушаются. Когда вы звоните,test()
вы передаетеCar
по значению. Итак, второйCar
становится копируемой и разрушается, когдаtest()
ее оставляют. Итак, у нас есть объяснение 3 деструкторам: первый и два последних в последовательности.Четвертый деструктор (второй в последовательности) неожиданный, и я не смог воспроизвести его с другими компиляторами.
Это может быть только временное
Car
создание в качестве источника дляCar
аргумента. Так как это не происходит при непосредственном предоставленииCar
значения в качестве аргумента, я подозреваю, что это для преобразованияTaxi
вCar
. Это неожиданно, посколькуCar
в каждом уже есть подобъектTaxi
. Поэтому я думаю, что компилятор делает ненужное преобразование в temp и не выполняет копирование, которое могло бы избежать этой температуры.Разъяснение дано в комментариях:
Вот пояснение со ссылкой на стандарт языка-юриста для проверки моих претензий:
[class.conv.ctor]
, то есть создание объекта одного класса (здесь Car) на основе аргумента другого типа (здесь Taxi).Car
значения. Компилятору будет разрешено сделать копию в соответствии с[class.copy.elision]/1.1
с тем, что вместо создания временного он может создать значение, которое будет возвращено непосредственно в параметр.Экспериментальное подтверждение анализа
Теперь я могу воспроизвести ваш случай, используя тот же компилятор, и провести эксперимент, чтобы подтвердить происходящее.
Мое предположение выше заключалось в том, что компилятор выбрал неоптимальный процесс передачи параметров, используя преобразование конструктора
Car(const &Taxi)
вместо копирования непосредственно изCar
подобъектаTaxi
.Поэтому я попытался позвонить,
test()
но явно бросилTaxi
вCar
.Моя первая попытка не смогла улучшить ситуацию. Компилятор все еще использовал неоптимальное преобразование конструктора:
Моя вторая попытка удалась. Он также выполняет приведение, но использует приведение указателей, чтобы настоятельно рекомендовать компилятору использовать
Car
подобъектTaxi
и без создания этого глупого временного объекта:И сюрприз: он работает как положено, выдает только 3 сообщения об уничтожении :-)
Завершающий эксперимент:
В последнем эксперименте я предоставил пользовательский конструктор путем преобразования:
и реализовать его с
*this = *static_cast<Car*>(&taxi);
. Звучит глупо, но это также генерирует код, который будет отображать только 3 сообщения деструктора, таким образом избегая ненужного временного объекта.Это приводит к мысли, что в компиляторе может быть ошибка, вызывающая такое поведение. Это означает, что при некоторых обстоятельствах возможность прямого конструирования копии из базового класса будет упущена.
источник
Taxi
может быть передана непосредственно вCar
конструктор копирования), поэтому удаление копии не имеет значения.