#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;
}
ini adalah output :
Car is destructed.
Car is destructed.
Taxi is destructed.
Car is destructed.
Saya menggunakan MS Visual Studio Community 2017 (Maaf, saya tidak tahu cara melihat edisi Visual C ++). Ketika saya menggunakan mode debug. Saya menemukan satu destruktor dijalankan ketika meninggalkan void test(Car c){ }
fungsi tubuh seperti yang diharapkan. Dan destruktor tambahan muncul ketika test(taxi);
selesai.
The test(Car c)
fungsi menggunakan nilai sebagai parameter formal. Mobil disalin ketika pergi ke fungsi. Jadi saya pikir hanya akan ada satu "Mobil hancur" ketika meninggalkan fungsi. Tetapi sebenarnya ada dua "Mobil hancur" ketika meninggalkan fungsi. (Baris pertama dan kedua seperti yang ditunjukkan dalam output) Mengapa ada dua "Mobil rusak"? Terima kasih.
===============
ketika saya menambahkan fungsi virtual class Car
misalnya: virtual void drive() {}
Lalu saya mendapatkan output yang diharapkan.
Car is destructed.
Taxi is destructed.
Car is destructed.
Taxi
objek ke fungsi yang mengambilCar
objek dengan nilai?Car
kemudian masalah ini menghilang dan memberikan hasil yang diharapkan.Jawaban:
Sepertinya kompiler Visual Studio mengambil sedikit jalan pintas ketika mengiris Anda
taxi
untuk pemanggilan fungsi, yang ironisnya menghasilkan itu melakukan lebih banyak pekerjaan daripada yang mungkin diharapkan.Pertama, ini mengambil Anda
taxi
dan menyalin-membangunCar
dari itu, sehingga argumen cocok.Kemudian, itu menyalin
Car
lagi untuk nilai pass-by-value.Perilaku ini hilang ketika Anda menambahkan copy constructor yang ditentukan pengguna, sehingga kompiler tampaknya melakukan ini karena alasannya sendiri (mungkin, secara internal, ini adalah jalur kode yang lebih sederhana), menggunakan fakta bahwa itu "diizinkan" karena salin itu sendiri sepele. Fakta bahwa Anda masih dapat mengamati perilaku ini menggunakan destructor non-sepele adalah sedikit penyimpangan.
Saya tidak tahu sejauh mana ini legal (terutama sejak C ++ 17), atau mengapa kompiler akan mengambil pendekatan ini, tetapi saya akan setuju bahwa itu bukan output yang saya harapkan secara intuitif diharapkan. Baik GCC atau Dentang melakukan ini, meskipun mungkin mereka melakukan hal-hal dengan cara yang sama tetapi kemudian lebih baik dalam menghilangkan salinan. Saya telah memperhatikan bahwa bahkan VS 2019 masih tidak hebat dalam jaminan elisi.
sumber
Apa yang terjadi ?
Saat Anda membuat
Taxi
, Anda juga membuatCar
subobjek. Dan ketika taksi dihancurkan, kedua benda hancur. Ketika Anda menelepon,test()
Anda melewati nilaiCar
dengan. Jadi yang keduaCar
akan dikopi dan akan hancur ketikatest()
dibiarkan. Jadi kami memiliki penjelasan untuk 3 destruktor: yang pertama dan yang kedua terakhir secara berurutan.Destructor keempat (yang kedua dalam urutan) tidak terduga dan saya tidak bisa mereproduksi dengan kompiler lain.
Itu hanya bisa
Car
dibuat sementara sebagai sumber untukCar
argumen. Karena itu tidak terjadi ketika memberikan langsungCar
nilai sebagai argumen, saya menduga itu untuk mengubah keTaxi
dalamCar
. Ini tidak terduga, karena sudah adaCar
sub proyek di setiapTaxi
. Oleh karena itu saya berpikir bahwa kompiler melakukan konversi yang tidak perlu menjadi temp dan tidak melakukan copy elision yang bisa menghindari temp ini.Klarifikasi yang diberikan dalam komentar:
Di sini klarifikasi dengan mengacu pada standar pengacara bahasa untuk memverifikasi klaim saya:
[class.conv.ctor]
, yaitu membangun objek dari satu kelas (di sini Mobil) berdasarkan argumen dari tipe lain (di sini Taksi).Car
nilainya. Kompiler akan diizinkan untuk membuat salinan salinan sesuai[class.copy.elision]/1.1
, karena alih-alih membangun sementara, itu dapat membangun nilai yang akan dikembalikan langsung ke parameter.Konfirmasi eksperimental anaysis
Saya sekarang dapat mereproduksi kasing Anda dengan menggunakan kompiler yang sama dan menggambar percobaan untuk mengonfirmasi apa yang sedang terjadi.
Asumsi saya di atas adalah bahwa kompiler memilih proses melewati parameter suboptimal, menggunakan konversi konstruktor
Car(const &Taxi)
alih-alih menyalin konstruksi langsung dariCar
subobjek dariTaxi
.Jadi saya mencoba menelepon
test()
tetapi secara eksplisit melemparkanTaxi
ke dalamCar
.Upaya pertama saya tidak berhasil memperbaiki situasi. Kompiler masih menggunakan konversi konstruktor suboptimal:
Upaya kedua saya berhasil. Itu melakukan casting juga, tetapi menggunakan pointer casting agar sangat menyarankan kompiler untuk menggunakan
Car
subobjek dariTaxi
dan tanpa membuat objek sementara konyol ini:Dan mengejutkan: berfungsi seperti yang diharapkan, hanya menghasilkan 3 pesan penghancuran :-)
Eksperimen penutup:
Dalam percobaan terakhir, saya memberikan konstruktor khusus dengan konversi:
dan mengimplementasikannya dengan
*this = *static_cast<Car*>(&taxi);
. Kedengarannya konyol, tetapi ini juga menghasilkan kode yang hanya akan menampilkan 3 pesan destruktor, sehingga menghindari objek sementara yang tidak perlu.Ini membuat kami berpikir bahwa mungkin ada bug di kompiler yang menyebabkan perilaku ini. Ini adalah kemungkinan salinan-konstruksi langsung dari kelas dasar akan terjawab dalam beberapa keadaan.
sumber
Taxi
dapat diteruskan langsung keCar
copy constructor), sehingga salinan salinan tidak relevan.