Yıkıcı neden operatör silme işleminde çağrılmıyor?


16

İçinde ::deletebir sınıf çağırmaya çalıştım operator delete. Ancak yıkıcı çağrılmaz.

Dersim tanımlanmış MyClassolan operator deleteaşırı. Küresel operator deletede aşırı yüklü. Aşırı operator deleteait MyClassAşırı yüklenmiş küresel arayacak operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

Çıktı:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Gerçek:

Aşırı çağırmadan önce yıkıcı ilgili yalnızca bir çağrı vardır operator deleteve MyClass.

Beklenen:

Yıkıcıya iki çağrı var. Aşırı çağırmadan önce biri operator deletearasında MyClass. Başka bir küresel çağırmadan önce operator delete.


6
MyClass::operator new()(en az) sizebaytlık ham bellek ayırmalıdır . Bir örneğini tamamen oluşturmaya çalışmamalıdır MyClass. Yapıcısı MyClasssonra yürütülür MyClass::operator new(). Daha sonra, içindeki deleteifade yıkıcıyı main()çağırır ve hafızayı serbest bırakır (yıkıcıyı tekrar çağırmadan). ::delete pİfade nesne türü hakkında bir bilgi alır p, çünkü en nokta pa, void *böylece yıkıcı refarans olarak,.
Peter


2
Zaten size verilen yanıtlar doğrudur, ama merak ediyorum: neden yeniyi geçersiz kılmaya ve silmeye çalışıyorsunuz? Tipik kullanım durumu, özel bellek yönetimi uygulamaktır (GC, varsayılan malloc'dan gelmeyen bellek (), vb.). Belki de elde etmeye çalıştığınız şey için yanlış aracı kullanıyorsunuzdur.
noamtm

2
::delete p;, türü *psilinmekte olan nesnenin türüyle (veya sanal yıkıcılı bir temel sınıfla) aynı olmadığından tanımlanmamış davranışa neden oluyor
MM

@MM Büyük derleyiciler sadece en fazla uyarıda bulunur, bu yüzden void*işlenen bile açıkça kötü biçimlendirilmiş olduğunu fark etmedim . [expr.delete] / 1 : " İşlenen , nesne türüne veya sınıf türüne işaretçi olacaktır. [...] Bu, void türünde bir işaretçi kullanılarak bir nesnenin void türünde bir işaretçi kullanılarak silinemeyeceği anlamına gelir . * "@OP Cevabımı değiştirdim.
ceviz

Yanıtlar:


17

Sen kötüye kullanıyorsun operator newve operator delete. Bu işleçler, ayırma ve ayırma işlevleridir. Nesneleri inşa etmekten veya tahrip etmekten sorumlu değildirler. Sadece nesnenin yerleştirileceği hafızayı sağlamaktan sorumludurlar.

Bu fonksiyonların global versiyonları ::operator newve ::operator delete. ::newve ::deletegibi yeni / silme-ifadelerdir vardır new/ delete, ki, farklılık gösteren ::newve ::deleteirade baypas sınıfa özel operator new/ operator deleteaşırı.

Yeni / delete-ifadeleri inşa eder / tahrip eder ve tahsis eder / yeniden tahsis eder (uygun olanı arayarak operator newveya operator deleteinşaattan önce veya yıkımdan sonra).

Senin aşırı yük ayırma / deallocation bölümü için sadece sorumlu olduğundan, çağırmalıdır ::operator newve ::operator deleteyerine ::newve ::delete.

deleteİçinde delete myClass;yıkıcı çağırmasını sorumludur.

::delete p;yıkıcıyı çağırmaz çünkü ptürü vardır void*ve bu nedenle ifade hangi yıkıcıyı çağıracağını bilemez. Silme ifadesine as işleneni yanlış biçimlendirilmiş ::operator deleteolsa da , muhtemelen belleği değiştirmek için değiştirileni çağırır (aşağıdaki düzenlemeye bakın).void*

::new MyClass();::operator newbellek ayırmak için değiştirileni çağırır ve içinde bir nesne oluşturur. Bu nesneye ait işaretçi , daha sonra bu bellekte başka bir nesne oluşturacak ve bir sonraki nesnenin yıkıcısını çağırmadan ömrünü sona erecek olan void*yeni ifadeye geri döndürülür .MyClass* myClass = new MyClass();


Düzenle:

@ MM'nin soruya yaptığı yorum sayesinde, bir void*işlenenin ::deleteaslında kötü biçimlendirildiğini fark ettim . ( [expr.delete] / 1 ) Ancak, büyük derleyiciler hata konusunda değil, sadece bu konuda uyarmaya karar vermişlerdir . O kullanarak, kötü şekillendirilmiş yapıldı Önce ::deletebir üzerinde void*vardı zaten tanımsız davranış, bkz bu soruyu .

Bu nedenle, programınız kötü biçimlendirilmiştir ve hala derlemeyi başarabiliyorsam, kodun yukarıda açıkladığım şeyi yaptığına dair herhangi bir garantiniz yoktur.


@SanderDeDycker tarafından cevabının altında belirtildiği gibi, tanımlanmamış bir davranışa da sahipsiniz, çünkü hafızada zaten MyClasso nesnenin yıkıcısını çağırmadan zaten bir nesne içeren başka bir nesne inşa ederek, [basic.life] / 5'i ihlal ediyorsanız program yıkıcının yan etkilerine bağlıdır. Bu durumda printfyıkıcıdaki ifadenin böyle bir yan etkisi vardır.


Yanlış kullanım, bu operatörün nasıl çalıştığını kontrol etmeyi amaçlamaktadır. Ancak, cevabınız için teşekkürler. Sorunumu çözen tek cevap gibi görünüyor.
1919'da

13

Sınıfa özgü aşırı yüklemeleriniz yanlış yapılır. Bu, çıktınızda görülebilir: yapıcı iki kez çağrılır!

Sınıfa özgü olarak operator newdoğrudan global operatörü arayın:

return ::operator new(size);

Benzer şekilde, sınıfa özgü olarak operator deleteşunları yapın:

::operator delete(p);

Daha operator newfazla ayrıntı için referans sayfasına bakın.


Ben yapıcı yeni :: operatörü yeni arayarak iki kez çağrılır biliyorum ve demek istediğim. Sorum şu: operatör silme :: delete operatörünü çağırırken neden yıkıcı çağrılmıyor?
1919'da

1
@expinc: Kasıtlı yıkıcı uğramadan yapıcı ikinci kez aradığını ilk gerçekten kötü bir fikirdir. Önemsiz yıkıcılar için (sizinki gibi), tanımlanmamış davranış bölgesine bile giriyorsunuz (eğer yıkıcıya yaptığınız yan etkilere bağlıysanız) - ref. [basic.life] §5 . Bunu yapma.
Sander De Dycker

1

CPP Referansına bakın :

operator delete, operator delete[]

Daha önce bir eşleştirme tarafından ayrılan depolama alanını ayırır operator new. Bu ayırma işlevleri, dinamik depolama süresine sahip nesneleri yok ettikten (veya oluşturmayı başaramadıktan) sonra belleği ayırmak için silme ifadeleri ve yeni ifadeler tarafından çağrılır. Bunlar ayrıca düzenli işlev çağrısı sözdizimi kullanılarak da çağrılabilir.

Silme (ve yeni) yalnızca 'bellek yönetimi' bölümünden sorumludur.

Bu nedenle, nesnenin örneğini temizlemek için yıkıcıya sadece bir kez çağrıldığı açıktır. İki kez çağrılırsa, her yıkıcı çağrılıp çağrılmadığını kontrol etmek zorunda kalacaktı.


1
Silme işleci, örneği sildikten sonra yıkıcıyı gösteren kendi günlüklerinden görebileceğiniz gibi, yıkıcıyı yine de örtülü olarak çağırmalıdır. Burada sorun, onun sınıf silme geçersiz kılma çağrısı :: delete, onun küresel silme geçersiz kılma yol açar. Bu genel silme geçersiz kılma yalnızca belleği serbest bırakır, böylece yıkıcıyı yeniden çağırmaz.
Turşu Rick

Referans, silme işleminin SONRA nesne ayrıştırması olarak adlandırıldığını açıkça belirtiyor
Mario The Spoon

Evet, genel silme, sınıf silme işleminden sonra çağrılır. Burada iki geçersiz kılma var.
Turşu Rick

2
@PickleRick - silme ifadesinin bir yıkıcı (yıkıcılı bir türe işaretçi sağlandığı varsayılarak) veya bir grup yıkıcı (dizi formu) çağırması gerektiği doğru olsa da , bir operator delete()işlev silme ifadesiyle aynı şey değildir. Yıkıcı operator delete()işlev çağrılmadan önce çağrılır.
Peter

1
Qoute öğesine bir başlık eklerseniz yardımcı olur. Şu anda ne ifade ettiği açık değildir. "Depolamayı ayırır ..." - depolamayı kim ayırır?
idclev 463035818
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.