Yıkıcı neden iki kez idam edildi?


12
#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;
}

bu çıktı :

Car is destructed.
Car is destructed.
Taxi is destructed.
Car is destructed.

MS Visual Studio Community 2017 kullanıyorum (Maalesef, Visual C ++ sürümlerini nasıl göreceğimi bilmiyorum). Hata ayıklama modunu kullandığımda. void test(Car c){ }İşlev gövdesini beklendiği gibi terk ederken bir yıkıcı yürütülür . Ve bittiğinde ekstra bir yıkıcı ortaya çıktı test(taxi);.

test(Car c)İşlev resmi parametresi olarak kullanılır. Bir Araba işleve giderken kopyalanır. Bu yüzden işlevden ayrılırken sadece bir "Araba imha edilecek" diye düşündüm. Ama aslında fonksiyondan ayrılırken iki "Araba imha edilir" (çıkışta gösterildiği gibi birinci ve ikinci satır) Neden iki "Araba imha edilir"? Teşekkür ederim.

===============

class Car Örneğin bir sanal işlevi eklediğimde : virtual void drive() {} Beklenen çıktıyı alıyorum.

Car is destructed.
Taxi is destructed.
Car is destructed.

3
Derleyici nasıl işleyeceğini bir sorun olabilir nesne dilimleme bir geçerken Taxibir alarak bir işleve nesneyi Cardeğeriyle nesneyi?
Bazı programcı dostum

1
Eski C ++ derleyiciniz olmalı. g ++ 9 beklenen sonuçları verir. Nesnenin fazladan bir kopyasının oluşturulma nedenini belirlemek için bir hata ayıklayıcı kullanın.
Sam Varshavchik

2
Sürüm 7.4.0 ile g ++ ve sürüm 6.0.0 ile clang ++ test ettim. Op'un çıktısından farklı beklenen çıktı verdiler. Yani problem onun kullandığı derleyici ile ilgili olabilir.
Marceline

1
MS Visual C ++ ile çoğalttım. Kullanıcı tanımlı bir kopya oluşturucu ve varsayılan kurucu Careklersem bu sorun kaybolur ve beklenen sonuçları verir.
interjay

1
Lütfen soruya derleyici ve sürüm ekleyin
Orbit'teki Lightness Races

Yanıtlar:


7

Visual Studio derleyicisi, taxiişlev çağrısı için dilimleme yaparken biraz kısayol alıyor gibi görünüyor , bu da ironik bir şekilde beklenenden daha fazla iş yapmasına neden oluyor.

İlk olarak, argümanınız eşleşecek şekilde taxibir kopyasını kendiniz alıyor ve Carondan bir kopya oluşturuyor .

Daha sonra, geçiş değeri için Car tekrar kopyalanıyor .

Bu davranış, kullanıcı tanımlı bir kopya oluşturucu eklediğinizde ortadan kalkar, bu nedenle derleyici bunu kendi nedenleriyle yapıyor olabilir (belki de dahili olarak, daha basit bir kod yoludur), kopyanın kendisi önemsizdir. Önemsiz bir yıkıcı kullanarak bu davranışı hala gözlemleyebilmeniz biraz sapmadır.

Bunun ne derece yasal olduğunu (özellikle C ++ 17'den beri) veya derleyicinin bu yaklaşımı neden alacağını bilmiyorum, ancak sezgisel olarak beklediğim çıktı olmadığını kabul ediyorum. Ne GCC ne de Clang bunu yapmazlar, ancak işleri aynı şekilde yapıyor olabilirler, ancak kopyayı elide tutma konusunda daha iyi olabilirler. Ben var hatta VS 2019 hala garanti elision değil harika olduğunu fark ettim.


Maalesef, "Derleyiciniz kopya seçimini yapmazsa, Taksi'den Arabaya dönüştürme" ile söylediğim tam olarak bu değil.
Christophe

Bu haksız bir sözdür, çünkü dilimlemeyi önlemek için refernece tarafından değere karşı geçiş, OP'nin bu sorunun ötesine yardımcı olmak için sadece bir düzenlemeye eklenmiştir. O zaman cevabım karanlıkta bir atış değildi, başlangıçtan itibaren açık bir şekilde açıklandı ve aynı sonuçlara geldiğinizi gördüğüme sevindim. Şimdi formülasyonunuza bakarak, "Görünüşe göre ... Bilmiyorum" diye düşünüyorum, burada aynı miktarda belirsizlik var, çünkü açıkçası ne ben ne de derleyicinin neden bu sıcaklığı üretmesi gerektiğini anlamıyorsunuz.
Christophe

Tamam, cevabınızın ilgisiz kısımlarını, ilgili tek paragrafı geride bırakarak kaldırın
Orbit'teki Lightness Races

Tamam, dikkat dağıtıcı dilimleme parasını kaldırdım ve standarda kesin referanslarla kopya elizyonuyla ilgili noktayı haklı çıkardım.
Christophe

Geçici bir arabanın neden Taksi'den kopyalanması ve sonra tekrar parametreye kopyalanması gerektiğini açıklayabilir misiniz? Ve derleyici düz bir araba ile sağlandığında neden bunu yapmıyor?
Christophe

3

Ne oluyor ?

Bir oluşturduğunuzda Taxi, bir Caralt nesne de oluşturursunuz . Taksi yok edildiğinde, her iki nesne de yok edilir. Aradığınızda by değerini test()geçersiniz Car. Böylece bir saniye Carkopya oluşturulur ve test()bırakıldığında imha edilir. Bu yüzden 3 yıkıcı için bir açıklamamız var: dizideki ilk ve ikisi.

Dördüncü yıkıcı (dizideki ikinci) beklenmedik ve diğer derleyicilerle çoğaltamadım.

Yalnızca argüman Cariçin kaynak olarak geçici olarak yaratılabilir Car. Doğrudan temin zaman olmaz beri Carargüman olarak değerini, onu dönüştürme şüpheli TaxiINTO Car. Bu beklenmedik bir durum, çünkü Carher birinde zaten bir alt nesne var Taxi. Bu nedenle derleyicinin geçici olarak gereksiz bir dönüşüm yaptığını ve bu sıcaklıktan kaçınabilecek kopya seçimini yapmadığını düşünüyorum.

Yorumlarda verilen açıklama:

Burada, dil avukatının iddialarımı doğrulaması standardına referansla açıklama:

  • Burada bahsettiğim dönüşüm, yapıcı tarafından bir dönüşüm [class.conv.ctor], yani başka bir türün (burada Taksi) bir argümana dayanan bir sınıfın (burada Araba) bir nesnesinin oluşturulmasıdır.
  • Bu dönüşüm daha sonra Cardeğerini döndürmek için geçici bir nesne kullanır . Derleyiciye, [class.copy.elision]/1.1geçici bir inşa etmek yerine, doğrudan parametreye döndürülecek değeri oluşturabileceğinden, bir kopya seçimine izin verilecektir .
  • Eğer bu sıcaklık yan etkiler veriyorsa, bunun nedeni derleyicinin bu olası kopya seçimini kullanmamasıdır. Kopya seçimi zorunlu olmadığı için yanlış değil.

Analizin deneysel teyidi

Şimdi aynı derleyiciyi kullanarak vakanızı yeniden oluşturabilir ve neler olduğunu doğrulamak için bir deneme yapabilirim.

Yukarıdaki varsayımım, derleyicinin Car(const &Taxi)doğrudan Caralt nesnesinden kopya oluşturma yerine yapıcı dönüşümünü kullanarak bir alt parametre geçirme işlemini seçmesiydi Taxi.

Bu yüzden aramayı denedim test()ama açıkça Taxibir Car.

İlk denemem durumu iyileştirmeyi başaramadı. Derleyici hala yetersiz yapıcı dönüşümünü kullanıyordu:

test(static_cast<Car>(taxi));  // produces the same result with 4 destructor messages

İkinci denemem başarılı oldu. Dökümü de yapar, ancak derleyiciye bu aptalca geçici nesneyi oluşturmadan ve Caralt Taxinesnesini kullanmasını şiddetle önermek için işaretçi dökümünü kullanır :

test(*static_cast<Car*>(&taxi));  //  :-)

Ve sürpriz: beklendiği gibi çalışıyor, sadece 3 imha mesajı üretiyor :-)

Sonuç deneyi:

Son bir denemede, dönüşümle özel bir oluşturucu sağladım:

 class Car {
 ... 
     Car(const Taxi& t);  // not necessary but for experimental purpose
 }; 

ve uygulamak *this = *static_cast<Car*>(&taxi);. Aptalca geliyor, ancak bu aynı zamanda sadece 3 yıkıcı mesajı görüntüleyecek ve böylece gereksiz geçici nesneden kaçınacak kod üretir.

Bu, derleyicide bu davranışa neden olan bir hata olabileceğini düşünmeye yol açar. Bazı durumlarda temel sınıftan doğrudan kopya oluşturma olasılığı kaçırılmış olabilir.


2
Soruya cevap vermiyor
Yörüngedeki Hafiflik Yarışları

1
@qiazi Bence bu, kopya elisyonsuz dönüşüm için geçici olan hipotezini doğrular, çünkü bu geçici arayanın bağlamında işlevden üretilir.
Christophe

1
"Derleyiciniz kopya seçimini yapmazsa Taksi'den Arabaya dönüşüm" derken, hangi kopya seçiminden bahsediyorsunuz? İlk etapta elden çıkarılması gereken bir kopya olmamalıdır.
interjay

1
@interjay, çünkü derleyicinin, taksiyi yapmak için Taksi'nin Car alt nesnesine dayalı bir Car geçici oluşturması ve ardından bu sıcaklığı Car parametresine kopyalaması gerekmez: kopyayı seçebilir ve parametreyi doğrudan orijinal alt nesneden oluşturabilir.
Christophe

1
Kopya elizyonu, standardın bir kopyanın oluşturulması gerektiğini belirtir, ancak belirli koşullar altında kopyanın elenmesine izin verir. Bu durumda, ilk etapta bir kopyanın oluşturulması için herhangi bir sebep yoktur (bir referans Taxidoğrudan Carkopya kurucuya geçirilebilir ), bu nedenle kopya elüsyonu önemsizdir.
interjay
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.