Sanal yıkıcılar ne zaman kullanılır?


1486

Çoğu OO teorisine dair sağlam bir anlayışa sahibim, ancak beni çok şaşırtan tek şey sanal yıkıcılar.

Zincirdeki her nesne için ve ne olursa olsun yıkıcı her zaman çağırılır diye düşündüm.

Bunları ne zaman sanallaştırmayı düşünüyorsunuz ve neden?


6
Şuna

146
Her yıkıcı aşağı ne olursa olsun çağrılır. virtualorta yerine yukarıdan başlamasını sağlar.
Mooing Duck


@MooingDuck bu biraz yanıltıcı bir yorum.
Euri Pinhollow

1
@FranklinYu sormanız iyi oldu çünkü şimdi bu yorumda herhangi bir sorun göremiyorum (yorumlarda cevap vermeye çalışmak dışında).
Euri Pinhollow

Yanıtlar:


1572

Sanal yıkıcılar, türetilmiş sınıfın bir örneğini temel sınıfa işaretçi yoluyla silebiliyorsanız yararlı olur:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Burada, Base'nin yıkıcısını ilan etmediğimi fark edeceksin virtual. Şimdi, aşağıdaki snippet'e bakalım:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Base yıkıcısının olmadığı virtualve bbir olan Base*bir işaret etme Derivednesnesi, delete bsahip tanımsız davranış :

[In delete b], silinecek nesnenin statik türü dinamik türünden farklıysa, statik tür silinecek nesnenin dinamik türünün temel sınıfı olmalı ve statik tür sanal bir yıkıcıya veya davranış tanımsız .

Çoğu uygulamada, yıkıcıya çağrı, sanal olmayan herhangi bir kod gibi çözülecektir, yani temel sınıfın yıkıcısı çağrılır, ancak türetilmiş sınıftan biri çağrılmaz ve kaynak sızıntısına neden olur.

Özetle, her zaman temel sınıfların yıkıcılarını virtualpolimorfik olarak manipüle edilmek istediklerinde yapın.

Temel sınıf işaretçisi aracılığıyla bir örneğin silinmesini önlemek istiyorsanız, temel sınıf yıkıcısını korumalı ve sanal olmayan yapabilirsiniz; derleyici deletetemel sınıf işaretçisini çağırmanıza izin vermez .

Herb Sutter'den bu makalede sanallık ve sanal temel sınıf yıkıcı hakkında daha fazla bilgi edinebilirsiniz .


174
Bu, daha önce yaptığım bir fabrikayı kullanarak neden büyük sızıntılar yaşadığımı açıklayacaktır. Şimdi hepsi mantıklı. Teşekkürler
Lodle

8
Veri üyesi olmadığı için bu kötü bir örnek. Ya Baseve Derivedsahip tüm otomatik depolama değişkenleri? yani yıkıcıda çalıştırılacak "özel" veya ek özel kod yoktur. Yıkıcı yazmaktan vazgeçmek uygun mudur? Yoksa türetilmiş sınıfta hala bellek sızıntısı olacak mı?
bobobobo


28
Herb Sutter'in makalesinden: "Yönerge # 4: Bir temel sınıf yıkıcısı, kamu ve sanal ya da korunmalı ve sanal olmayan olmalıdır."
Pazar

3
Ayrıca makaleden - 'sanal bir yıkıcı olmadan polimorfik olarak silerseniz, "tanımlanmamış davranış" ın korkunç hayaletini çağırırsınız, kişisel olarak orta derecede iyi aydınlatılmış bir sokakta bile tanışmayacağım bir hayalet, çok teşekkür ederim.' lol
Bondolin

219

Sanal bir yapıcı mümkün değildir, ancak sanal yıkıcı mümkündür. Deneyelim ...

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Yukarıdaki kod aşağıdakileri çıkarır:

Base Constructor Called
Derived constructor called
Base Destructor called

Türetilmiş nesnenin yapımı, inşaat kuralını takip eder, ancak "b" işaretleyicisini (temel işaretçi) sildiğimizde, yalnızca temel yıkıcının çağrıldığını bulduk. Ama bu olmamalı. Uygun şeyi yapmak için, temel yıkıcıyı sanal yapmalıyız. Şimdi aşağıdakilerde neler olduğunu görelim:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Çıktı aşağıdaki gibi değişti:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

Böylece temel imlecin imhası (türetilmiş nesneye tahsis edilir!) İmha kuralını, yani önce Türetilmiş, daha sonra Üssü izler. Öte yandan, sanal bir kurucuya benzemez.


1
"sanal kurucu mümkün değildir" sanal kurucuyu kendiniz yazmanız gerekmediği anlamına gelir. Türetilmiş nesnenin inşası türetilmişten tabana inşa zincirini takip etmelidir. Bu yüzden kurucunuz için sanal anahtar sözcüğü yazmanıza gerek yoktur. Teşekkürler
Tunvir Rahman Tusher

4
@Murkantilizm, "sanal yapıcılar yapılamaz" aslında doğrudur. Bir kurucu sanal olarak işaretlenemez.
cmeub

1
@cmeub, Ama sanal bir kurucudan istediğini elde etmek için bir deyim var. Bkz. Parashift.com/c++-faq-lite/virtual-ctors.html
cape1232

@TunvirRahmanTusher neden Base Destructor'ın çağrıldığını açıklayabilir misiniz?
rimalonfire


195

Polimorfik baz sınıflarında yıkıcıları sanal ilan edin. Bu Scott Meyers'ın Etkili C ++ 'sinde Madde 7'dir . Meyers, bir sınıfın herhangi bir sanal işlevi varsa, sanal bir yıkıcıya sahip olması gerektiğini ve temel sınıflar olarak tasarlanmamış veya polimorfik olarak kullanılmak üzere tasarlanmamış sınıfların sanal yıkıcılar ilan etmemesi gerektiğini özetlemektedir .


14
+ "Bir sınıfın herhangi bir sanal işlevi varsa, sanal bir yıkıcıya sahip olmalı ve temel sınıflar olarak tasarlanmamış veya polimorfik olarak kullanılmak üzere tasarlanmamış sınıflar sanal yıkıcılar bildirmemelidir.": Mantıklı olduğu durumlar var mı? bu kuralı çiğnemek mi? Değilse, derleyicinin bu koşulu kontrol etmesi ve bir hata vermesi tatmin edici değil mi?
Giorgio

@Giorgio Kuralın herhangi bir istisnasını bilmiyorum. Ama kendimi bir C ++ uzmanı olarak değerlendirmezdim, bu yüzden bunu ayrı bir soru olarak göndermek isteyebilirsiniz. Bir derleyici uyarısı (veya statik analiz aracından gelen bir uyarı) bana mantıklı geliyor.
Kertenkele Bill

10
Sınıflar, belirli bir türün işaretçisi aracılığıyla silinmeyecek şekilde tasarlanabilir, ancak yine de sanal işlevlere sahiptir - tipik örnek bir geri arama arabirimidir. Biri sadece abone olmak için olduğu gibi bir geri arama arayüzü işaretçisi aracılığıyla uygulamasını silmez, ancak sanal işlevleri vardır.
dascandy

3
@dascandy Tam - o ya tüm Birçok biz polimorfik davranış kullanan ancak göstericiler üzerinden depolama yönetimini yapmazlar diğer durumlar - sadece gözlem yolları olarak kullanılan işaretçiler ile, örneğin otomatik veya statik süreli nesneleri koruyarak. Bu gibi durumlarda sanal bir yıkıcıyı uygulamaya gerek yoktur. Burada sadece insanlardan alıntı yaptığımız için Sutter'i yukarıdan tercih ediyorum: "Yönerge # 4: Bir temel sınıf yıkıcısı hem kamu hem de sanal veya korumalı ve sanal olmayan olmalıdır." İkincisi, yanlışlıkla bir temel işaretçi aracılığıyla silmeye çalışan herkesin yollarının hatasını göstermesini sağlar
underscore_d

1
@Giorgio Aslında bir yıkıcıya sanal bir çağrı kullanmak ve önlemek için bir hile vardır: bir const referansı ile türetilmiş bir nesne gibi bir tabana bağlamak const Base& = make_Derived();. Bu durumda, Derivedsanal olmasa bile , değerin yıkıcısı çağrılır, böylece kişi vtables / vpointers tarafından yüklenen yükü kurtarır. Tabii ki kapsam oldukça sınırlıdır. Andrei Alexandrescu, Modern C ++ Design adlı kitabında bundan bahsetti .
vsoftco

46

Ayrıca, sanal bir yıkıcı olmadığında bir temel sınıf işaretçisinin silinmesinin tanımsız davranışa neden olacağını unutmayın . Son zamanlarda öğrendiğim bir şey:

C ++ 'da geçersiz kılmanın silinmesi nasıl davranmalıdır?

Yıllardır C ++ kullanıyorum ve hala kendimi asmayı başardım.


Bu sorunuzu inceledim ve temel yıkıcıyı sanal olarak ilan ettiğinizi gördüm. Peki "sanal yıkıcı olmadığında bir temel sınıf işaretçisinin silinmesi, tanımlanmamış davranışla sonuçlanacaktır" sorunuzla ilgili olarak geçerli kalıyor mu? Bu soruda, sil komutunu çağırdığınızda, türetilmiş sınıf (yeni operatörü tarafından oluşturulan) önce uyumlu bir sürüm için kontrol edilir. Orada bir tane bulduğu için çağrıldı. Öyleyse, "hiçbir yıkıcı olmadığında bir temel sınıf işaretçisinin silinmesi tanımsız davranışa neden olacak" olarak söylemenin daha iyi olacağını düşünmüyor musunuz?
ubuntugod

Bu hemen hemen aynı şey. Varsayılan kurucu sanal değildir.
BigSandwich

41

Sınıfınız her polimorfik olduğunda yıkıcıyı sanal hale getirin.


13

Bir temel sınıfa işaretçi aracılığıyla yıkıcı çağırmak

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

Sanal yıkıcı çağrısı, diğer sanal işlev çağrılarından farklı değildir.

Çünkü base->f()çağrı çağrılır Derived::f()ve aynı base->~Base()- geçersiz kılma işlevi - Derived::~Derived()çağrılır.

Yıkıcı dolaylı olarak çağrıldığında da olur, örn delete base;. deleteDeyim arayacak base->~Base()sevk edileceği Derived::~Derived().

Sanal olmayan yıkıcı ile soyut sınıf

Nesneyi bir işaretçi aracılığıyla temel sınıfına silmeyecekseniz, sanal bir yıkıcıya gerek yoktur. Sadece bunu yapmak protectedyanlışlıkla denilen olmayacak şekilde:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

~Derived()Sadece türetilmiş olsa bile, tüm türetilmiş sınıflarda açıkça beyan etmek gerekli ~Derived() = defaultmidir? Yoksa bu dil tarafından ima ediliyor mu (atlamayı güvenli kılıyor)?
Ponkadoodle

@Wallacoloo hayır, sadece gerektiğinde beyan edin. Örneğin, protectedbölüm koymak veya kullanarak sanal olmasını sağlamak için override.
Abyx

9

Arayüzler ve arayüz uygulamaları üzerine düşünmeyi seviyorum. C ++ konuşmak arayüzü saf sanal sınıftır. Yıkıcı arayüzün bir parçasıdır ve uygulanması beklenmektedir. Bu nedenle yıkıcı saf sanal olmalıdır. Nasıl yapıcı hakkında? Yapıcı aslında arayüzün bir parçası değildir çünkü nesne her zaman açık bir şekilde somutlaştırılmıştır.


2
Aynı soruya farklı bir bakış açısı. Temel sınıf ve türetilmiş sınıf yerine arayüzler açısından düşünürsek, bu doğal bir sonuçtur: arayüzün bir parçasıysa sanal hale getirmek yerine. Eğer değilse.
Dragan Ostojiç

2
OO arayüz kavramının ve bir C ++ saf sanal sınıfının benzerliğini belirtmek için +1 . Yıkıcı ile ilgili olarak uygulanması bekleniyor : bu genellikle gereksizdir. Bir sınıf ham dinamik olarak ayrılan bellek (örneğin, akıllı bir işaretçi aracılığıyla değil), bir dosya tanıtıcısı veya bir veritabanı tanıtıcısı gibi bir kaynağı yönetmedikçe, derleyici tarafından oluşturulan varsayılan yıkıcıyı kullanarak türetilmiş sınıflarda iyidir. Ve bir yıkıcı (veya herhangi bir işlev) virtualbir temel sınıfta bildirilirse, bildirilmese virtualbile otomatik olarak türetilmiş bir sınıfta olduğunu unutmayın.
DavidRR

Bu, yıkıcının mutlaka arayüzün bir parçası olmadığı önemli ayrıntıyı kaçırır . Polimorfik fonksiyonları olan ancak arayanın yönetmediği / silmesine izin verilmeyen sınıflar kolayca programlanabilir. O zaman sanal bir yıkıcının hiçbir amacı yoktur. Elbette, bunu sağlamak için, sanal olmayan - muhtemelen varsayılan - yıkıcı halka açık olmamalıdır. Tahmin etmek zorunda kalsaydım, bu tür sınıfların projelerde dahili olarak daha sık kullanıldığını söyleyebilirim, ancak bu onları tüm örneklerde / nüans olarak daha az alakalı hale getirmez.
underscore_d

8

Temel sınıf işaretçisi aracılığıyla nesneler silinirken farklı yıkıcıların uygun sıraya uymasını istediğinizde yıkıcı için sanal anahtar kelime gereklidir. Örneğin:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

Temel sınıf yıkıcınız sanal ise, nesneler bir sırayla yok edilir (önce türetilen nesne sonra taban). Temel sınıf yıkıcınız sanal DEĞİL ise, yalnızca temel sınıf nesnesi silinir (çünkü işaretçi "Base * myObj" temel sınıfındadır). Böylece türetilmiş nesne için bellek sızıntısı olacaktır.


7

Basit olmak gerekirse, Sanal yıkıcı türetilmiş sınıf nesnesine işaret eden bir temel sınıf işaretçisini sildiğinizde kaynakları uygun bir sırayla imha etmektir.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


Temel sanal yıkıcıya sahip olmamak ve deletebir temel işaretçiyi çağırmak tanımsız davranışa yol açar.
James Adkison

@JamesAdkison neden tanımsız davranışa yol açıyor ??
rimalonfire

@rimiro Standardın söylediği şey bu . Bir kopyam yok, ancak bağlantı sizi birisinin standart içindeki konuma başvurduğu bir yoruma götürür.
James Adkison

@rimiro "Bu nedenle, silme, temel sınıf arabirimi aracılığıyla polimorfik olarak gerçekleştirilebiliyorsa, sanal olarak davranmalı ve sanal olmalıdır. Gerçekten, dil gerektirir - sanal bir yıkıcı olmadan polimorfik olarak silerseniz, korkunç hayali "tanımsız davranış," kişisel olarak orta derecede iyi aydınlatılmış bir sokakta bile tanışmayacağım bir hayalet, çok teşekkür ederim. " ( gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison

4

Sanal temel sınıf yıkıcılar "en iyi uygulama" dır - bellek sızıntılarını önlemek için (tespit edilmesi zor) her zaman bunları kullanmalısınız. Bunları kullanarak, sınıflarınızın miras zincirindeki tüm yıkıcıların arılandığından emin olabilirsiniz (uygun sırayla). Sanal yıkıcı kullanarak bir temel sınıftan miras almak, miras sınıfının yıkıcısını da otomatik olarak sanal hale getirir (bu nedenle miras sınıf yıkıcı bildirgesinde 'sanal' yazmanız gerekmez).


4

Eğer kullanırsanız shared_ptr(sadece Shared_ptr, unique_ptr değil), sanal taban sınıfı yıkıcı olması gerekmez:

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

çıktı:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

Bu mümkün olsa da, kimsenin bunu kullanmasını caydırırdım. Sanal bir yıkıcının yükü miniktir ve bu sadece bunu bilmeyen daha az deneyimli bir programcı tarafından batırmayı mümkün kılar. Bu küçük virtualanahtar kelime sizi çok acı çekebilir.
Michal Štein

3

Sanal yıkıcı nedir veya sanal yıkıcı nasıl kullanılır

Sınıf yıkıcısı, sınıfın ayırdığı belleği yeniden tahsis edecek olan, ~ ile başlayan sınıfın aynı adına sahip bir işlevdir. Neden sanal bir yıkıcıya ihtiyacımız var?

Bazı sanal işlevlere sahip aşağıdaki örneğe bakın

Örnek ayrıca bir mektubu nasıl daha büyük veya daha aşağıya dönüştürebileceğinizi söyler

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

Yukarıdaki örnekten hem MakeUpper hem de MakeLower sınıfı için yıkıcı çağrılmadığını görebilirsiniz.

Sanal yıkıcı ile bir sonraki örneğe bakın

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

Sanal yıkıcı, nesneyi uygun bir şekilde temizleyebilmek için açıkça sınıfın en türetilmiş çalışma zamanı yıkıcısını çağırır.

Veya bağlantıyı ziyaret edin

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


2

temel sınıftan türetilmiş sınıf yıkıcı çağırmanız gerektiğinde. temel sınıfta sanal temel sınıf yıkıcı bildirmeniz gerekir.


2

Bu sorunun özünün, yıkıcı değil, sanal yöntemler ve polimorfizm hakkında olduğunu düşünüyorum. İşte daha net bir örnek:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Yazdırılacak:

This is B.

O olmadan virtualyazdıracak:

This is A.

Ve şimdi sanal yıkıcıları ne zaman kullanacağınızı anlamalısınız.


Hayır, bu sadece sanal işlevlerin tam temellerini geri çeker, yıkıcının ne zaman / neden olması gerektiği nüansını tamamen göz ardı eder - ki bu kadar sezgisel değil, bu yüzden OP neden soruyu sordu. (Ayrıca, neden gereksiz dinamik ayırma? Sadece yapın B b{}; A& a{b}; a.foo();. Yanlış girintiyle - önce - NULLolması gereken nullptr- kontrol edilmesi deletegerekli değildir: delete nullptr;hayır-op olarak tanımlanmıştır. Bir şey varsa, aramadan önce bunu kontrol etmelisiniz ->foo(), aksi takdirde tanımlanamayan davranış bir newşekilde başarısız olursa ortaya çıkabilir .)
underscore_d

2
deleteBir NULLişaretçiyi aramak güvenlidir (yani, if (a != NULL)korumaya ihtiyacınız yoktur ).
James Adkison

@SaileshD Evet, biliyorum. Ben söylediğim budur benim yorum
James Adkison

1

Ben sanal tanımlayıcı olmadan bir temel sınıf (/ struct) veya daha kesin olarak hiçbir vtable silerken oluşabilecek "tanımlanmamış" davranış, ya da en azından "çökme" tanımsız davranış tartışmak için yararlı olacağını düşündüm. Aşağıdaki kod birkaç basit yapıyı listeler (aynı sınıflar için de geçerlidir).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Sanal yıkıcılara ihtiyacınız olup olmadığını önermiyorum, ancak genel olarak onlara sahip olmak iyi bir uygulama olduğunu düşünüyorum. Ben sadece temel sınıf (/ struct) bir vtable yoksa ve türetilmiş sınıf (/ struct) yapar ve bir temel sınıf (/ struct) ile bir nesneyi silmek bir çökme ile sonuçlanabilir nedeni işaret ediyorum Işaretçi. Bu durumda, yığının serbest yordamına ilettiğiniz adres geçersizdir ve bu nedenle kilitlenmenin nedeni.

Yukarıdaki kodu çalıştırırsanız, sorunun ne zaman oluştuğunu açıkça görürsünüz. Temel sınıfın (/ struct) bu işaretçisi türetilmiş sınıfın (/ struct) bu işaretçisinden farklı olduğunda, bu sorunla karşılaşırsınız. Yukarıdaki örnekte, a ve b yapısında vtable yoktur. c ve d yapılarında vtable vardır. Bu nedenle, ac veya d nesne örneğine yönelik a veya b işaretçisi, vtable'ı hesaba katacak şekilde sabitlenir. Silmek için bu a veya b işaretçisini iletirseniz, adres yığının ücretsiz rutini için geçersiz olduğundan çökecektir.

Temel sınıf işaretçilerinden vtables olan türetilmiş örnekleri silmeyi planlıyorsanız, temel sınıfın bir vtable olduğundan emin olmanız gerekir. Bunu yapmanın bir yolu, kaynakları düzgün bir şekilde temizlemek için istediğiniz sanal bir yıkıcı eklemektir.


0

Hakkında temel bir tanım virtual, bir sınıfın üye işlevinin türetilmiş sınıflarında gereğinden fazla kullanılamayacağını belirler.

Bir sınıfın D-tor'u temelde kapsamın sonunda çağrılır, ancak bir sorun var, örneğin Yığın üzerinde bir örnek tanımladığımızda (dinamik ayırma), onu manuel olarak silmeliyiz.

Talimat yürütülür yürütülmez, temel sınıf yıkıcı çağrılır, ancak türetilmiş olan için çağrılmaz.

Pratik bir örnek, kontrol alanında efektörleri, aktüatörleri manipüle etmeniz gerektiğidir.

Kapsamın sonunda, güç elemanlarından birinin (Aktüatör) yıkıcısı çağrılmazsa, ölümcül sonuçlar olacaktır.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}

-1

Kamusal olarak miras alınan, polimorfik olsun ya da olmasın her sınıfın sanal bir yıkıcıya sahip olması gerekir. Başka bir deyişle, bir temel sınıf işaretçisi ile gösterilebiliyorsa, temel sınıfının sanal bir yıkıcıya sahip olması gerekir.

Sanal ise, türetilmiş sınıf yıkıcı çağrılır, sonra temel sınıf yapıcısı. Sanal değilse, yalnızca temel sınıf yıkıcısı çağrılır.


Ben sadece gerekli olduğunu söyleyebilirim "Eğer bir temel sınıf işaretçi ile işaret edilebilir " ve genel olarak silinebilir. Ama sanırım daha sonra ihtiyaç duyulacaksa sanal dtors ekleme alışkanlığına girmenin zararı yok.
underscore_d
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.