Sanal yıkıcıları ne zaman kullanmamalısınız?


Yanıtlar:


72

Aşağıdakilerden herhangi biri doğru olduğunda sanal bir yıkıcı kullanmaya gerek yoktur:

  • Ondan sınıf çıkarma niyeti yok
  • Yığın üzerinde örnekleme yok
  • Bir süper sınıfın göstericisinde saklama niyeti yok

Hafıza için gerçekten bu kadar baskı altında olmadığınız sürece bundan kaçınmak için belirli bir neden yok.


25
Bu iyi bir cevap değil. "İhtiyaç yok", "olmamalı" dan farklıdır ve "niyet yok", "imkansız hale getirilmeli" den farklıdır.
Windows programcısı

5
Ayrıca şunu ekleyin: bir örneği temel sınıf işaretçisi aracılığıyla silme niyeti yoktur.
Adam Rosenfield

9
Bu gerçekten soruya cevap vermiyor. Sanal dtor kullanmamak için iyi nedeniniz nerede?
mxcl

9
Bir şeyi yapmaya gerek olmadığında, bunu yapmamak için iyi bir neden olduğunu düşünüyorum. XP'nin Basit Tasarım ilkesini takip ediyor.
Eylül

12
"Niyetiniz yok" diyerek, sınıfınızın nasıl kullanılacağına dair büyük bir varsayımda bulunuyorsunuz. Bana öyle geliyor ki, çoğu durumda en basit çözüm (bu nedenle varsayılan olmalıdır) sanal yıkıcılara sahip olmak ve bunlardan yalnızca belirli bir nedeniniz yoksa kaçınmak olmalıdır. Bu yüzden hala iyi bir nedenin ne olacağını merak ediyorum.
ckarras

68

Eğer gerekirken, yani açıkça soruya cevap vermek için değil sanal yıkıcı beyan ederim.

C ++ '98 / '03

Sanal bir yıkıcı eklemek, sınıfınızın POD (düz eski veriler) * olmasını veya bir araya getirilmesini POD olmayan hale getirebilir . Sınıf türünüz bir yerde toplu olarak başlatılmışsa bu, projenizin derlenmesini durdurabilir.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

Ekstrem bir durumda, böyle bir değişiklik, sınıfın bir POD gerektiren bir şekilde kullanıldığı, örneğin bir elips parametresi aracılığıyla geçirilmesi veya memcpy ile kullanılması gibi, tanımlanmamış davranışlara da neden olabilir.

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[* POD türü, bellek düzeni hakkında belirli garantileri olan bir türdür. Standart gerçekten sadece POD tipi bir nesneden bir karakter dizisine (veya işaretsiz karakterlere) kopyalayıp tekrar geri dönerseniz, sonucun orijinal nesneyle aynı olacağını söylüyor.]

Modern C ++

C ++ 'nın son sürümlerinde, POD kavramı, sınıf düzeni ile onun inşası, kopyalanması ve yok edilmesi arasında bölünmüştür.

Üç nokta durumu için, artık tanımsız davranış değildir, artık uygulama tanımlı anlambilimle koşullu olarak desteklenmektedir (N3937 - ~ C ++ '14 - 5.2.2 / 7):

... Önemsiz olmayan bir kopya oluşturucusuna, önemsiz olmayan bir taşıma yapıcısına veya karşılık gelen bir parametre olmadan önemsiz bir yıkıcıya sahip potansiyel olarak değerlendirilmiş bir sınıf türü bağımsız değişkeni (Madde 9) geçirmek, uygulama ile koşullu olarak desteklenir. tanımlanmış anlambilim.

Bunun dışında bir yıkıcı ilan =defaultetmek, bunun önemsiz olmadığı anlamına gelecektir (12.4 / 5)

... Yıkıcı, kullanıcı tarafından sağlanmadıysa önemsizdir ...

Modern C ++ 'da yapılan diğer değişiklikler, bir yapıcı eklenebildiğinden toplu başlatma sorununun etkisini azaltır:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}

1
Haklısın ve yanılmışım, performans tek sebep değil. Ancak bu, geri kalanı hakkında haklı olduğumu gösteriyor: Sınıfın programcısı, sınıfın bir başkası tarafından miras alınmasını önlemek için kodu dahil etse iyi olur.
Windows programcısı

sevgili Richard, yazdıkların hakkında biraz daha yorum yapabilir misin? Demek istediğini anlamıyorum, ancak googling yaparak bulduğum tek değerli nokta bu gibi görünüyor) Veya daha ayrıntılı bir açıklamaya bir bağlantı verebilir misiniz?
John Smith

1
@JohnSmith Cevabı güncelledim. Umarım bu yardımcı olur.
Richard Corden

28

Yalnızca ve yalnızca sanal yöntemlerim varsa bir sanal yıkıcı bildiririm. Sanal yöntemlere sahip olduğumda, onu yığın üzerinde örneklemekten veya temel sınıfa bir işaretçi depolamaktan kaçınacağım konusunda kendime güvenmiyorum. Bunların her ikisi de son derece yaygın işlemlerdir ve yıkıcı sanal olarak ilan edilmezse kaynakları sessizce sızdırır.


3
Ve aslında, gcc'de tam olarak bu durumda uyarı veren bir uyarı seçeneği vardır (sanal yöntemler ancak sanal dtor yok).
CesarB

6
Başka sanal işlevleriniz olsun ya da olmasın, sınıftan türetiyorsanız bellek sızıntısı riskini almıyor musunuz?
Mag Roader

1
Mag ile aynı fikirdeyim. Sanal bir yıkıcının ve / veya sanal yöntemin bu kullanımı ayrı gereksinimlerdir. Sanal yıkıcı, bir sınıfa temizleme (örn. Belleği silme, dosyaları kapatma, vb.) Yapma yeteneği sağlar VE ayrıca tüm üyelerinin kurucularının çağrılmasını sağlar.
user48956

7

deleteSınıfınızın türüne sahip bir alt sınıfın nesnesine bir işaretçide çağrılma şansı olduğunda, sanal bir yıkıcıya ihtiyaç duyulur . Bu, derleyicinin derleme zamanında öbek üzerindeki bir nesnenin sınıfını bilmesine gerek kalmadan çalışma zamanında doğru yıkıcının çağrılmasını sağlar. Örneğin, Başağıdakilerin bir alt sınıfı olduğunu varsayalım A:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

Kodunuz performans açısından kritik değilse, yalnızca güvenlik için yazdığınız her temel sınıfa sanal bir yıkıcı eklemek mantıklı olacaktır.

Bununla birlikte, kendinizi deletesıkı bir döngüde çok sayıda nesnenin içinde bulduysanız , sanal bir işlevi (boş olsa bile) çağırmanın performans ek yükü fark edilebilir. Derleyici genellikle bu çağrıları satır içi yapamaz ve işlemci nereye gideceğini tahmin etmekte zorlanabilir. Bunun performans üzerinde önemli bir etkisi olması muhtemel değildir, ancak bahsetmeye değer.


"Kodunuz performans açısından kritik değilse, yalnızca güvenlik için yazdığınız her temel sınıfa sanal bir yıkıcı eklemek mantıklı olacaktır." gördüğüm her cevapta daha fazla vurgulanmalı
csguy

5

Sanal işlevler, tahsis edilen her nesnenin bir sanal işlev tablosu işaretçisi tarafından bellek maliyetinde artması anlamına gelir.

Bu nedenle, programınız çok fazla sayıda nesneyi ayırmayı içeriyorsa, nesne başına ek 32 bit kaydetmek için tüm sanal işlevlerden kaçınmaya değer.

Diğer tüm durumlarda, dtoru sanal yapmak için hata ayıklama sefaletinden kurtulacaksınız.


1
Sadece nit-toplama, ama bu günlerde bir işaretçi genellikle 32 yerine 64 bit olacaktır.
Head Geek

5

Dinamik polimorfizm ile tüm C ++ sınıfları temel sınıf olarak kullanılmaya uygun değildir.

Sınıfınızın dinamik polimorfizm için uygun olmasını istiyorsanız, yıkıcısı sanal olmalıdır. Ek olarak, bir alt sınıfın muhtemelen geçersiz kılmak isteyebileceği herhangi bir yöntem (bu, tüm genel yöntemler artı potansiyel olarak dahili olarak kullanılan bazı korumalı yöntemler anlamına gelebilir) sanal olmalıdır.

Sınıfınız dinamik polimorfizm için uygun değilse, yıkıcı sanal olarak işaretlenmemelidir, çünkü bunu yapmak yanıltıcıdır. İnsanları sınıfınızı yanlış kullanmaya teşvik ediyor.

Yıkıcı sanal olsa bile dinamik polimorfizm için uygun olmayacak bir sınıf örneği:

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

Bu sınıfın tüm amacı RAII için yığının üzerine oturmaktır. Alt sınıfları bir yana, bu sınıftaki nesnelere işaretçiler iletiyorsanız, o zaman Yanlış Yapıyorsunuz demektir.


2
Polimorfik kullanım, polimorfik silme anlamına gelmez. Bir sınıfın sanal yöntemlere sahip olması, ancak sanal yıkıcı olmaması için pek çok kullanım durumu vardır. Hemen hemen her GUI araç setinde tipik bir statik olarak tanımlanmış iletişim kutusunu düşünün. Ana pencere, alt nesneleri yok eder ve her birinin tam türünü bilir, ancak tüm alt pencereler, metnin metnini getiren vuruş testi, çizim, erişilebilirlik API'leri gibi herhangi bir sayıda yerde polimorfik olarak da kullanılacaktır. konuşma motorları, vb.
Ben Voigt

4
Doğru, ancak soru soran kişi, sanal bir yıkıcıdan özellikle ne zaman kaçınmanız gerektiğini soruyor. Tanımladığınız iletişim kutusu için, sanal bir yıkıcı anlamsızdır, ancak IMO zararlı değildir. Bir temel sınıf işaretçisi kullanarak bir iletişim kutusunu silmem gerekmeyeceğinden emin değilim - örneğin, gelecekte ana penceremin fabrikaları kullanarak alt nesnelerini oluşturmasını isteyebilirim. Yani bu sanal yıkıcıdan kaçınma meselesi değil , sadece bir tanesine sahip olmak için uğraşmayabilirsiniz. Derivasyon için uygun değildir bir sınıf üzerinde sanal bir yıkıcı olan bu yanıltıcı çünkü, ama, zararlı.
Steve Jessop

4

Bir yıkıcıyı sanal olarak bildirmemek için iyi bir neden, sınıfınızı sanal bir işlev tablosunun eklenmesinden kurtarmasıdır ve mümkün olduğunda bundan kaçınmalısınız.

Pek çok insanın sadece güvenli tarafta olmak için yıkıcıları her zaman sanal olarak ilan etmeyi tercih ettiğini biliyorum. Ancak sınıfınızın başka sanal işlevleri yoksa, sanal bir yıkıcıya sahip olmanın gerçekten, gerçekten hiçbir anlamı yoktur. Sınıfınızı, başka sınıfları ondan çıkaran diğer insanlara verseniz bile, sınıfınıza yükseltilmiş bir işaretçide silme işlemini çağırmak için hiçbir sebepleri olmayacaktır - ve eğer yaparsa, bunu bir hata olarak kabul ederim.

Tamam, tek bir istisna var, yani sınıfınız türetilmiş nesnelerin polimorfik silinmesini (yanlış) gerçekleştirmek için kullanılıyorsa, ancak o zaman siz - veya diğer çocuklar - bunun sanal bir yıkıcı gerektirdiğini umuyoruz.

Başka bir deyişle, sınıfınızda sanal olmayan bir yıkıcı varsa, bu çok açık bir ifadedir: "Türetilmiş nesneleri silmek için beni kullanmayın!"


3

Çok sayıda örneğe sahip çok küçük bir sınıfınız varsa, bir vtable işaretçisinin ek yükü, programınızın bellek kullanımında bir fark yaratabilir. Sınıfınızın başka herhangi bir sanal yöntemi olmadığı sürece, yıkıcıyı sanal olmayan yapmak bu ek yükü kurtaracaktır.


1

Genellikle yıkıcıyı sanal olarak bildiririm, ancak bir iç döngüde kullanılan performans açısından kritik kodunuz varsa, sanal tablo aramasından kaçınmak isteyebilirsiniz. Bu, çarpışma kontrolü gibi bazı durumlarda önemli olabilir. Ancak kalıtımı kullanırsanız bu nesneleri nasıl yok edeceğinize dikkat edin, yoksa nesnenin yalnızca yarısını yok edersiniz.

Sanal tablo aramasının, bir nesne üzerindeki herhangi bir yöntem sanalsa, bir nesne için gerçekleştiğini unutmayın . Dolayısıyla, sınıfta başka sanal yöntemleriniz varsa, bir yıkıcı üzerindeki sanal belirtimi kaldırmanın anlamı yok.


1

Sınıfınızda bir vtable bulunmadığından kesinlikle emin olmanız gerekiyorsa, o zaman sanal bir yıkıcıya da sahip olmamanız gerekir.

Bu nadir bir durum, ama oluyor.

Bunu yapan bir modelin en bilinen örneği DirectX D3DVECTOR ve D3DMATRIX sınıflarıdır. Bunlar sözdizimsel şeker için işlevler yerine sınıf yöntemleridir, ancak sınıfların işlev yükünden kaçınmak için kasıtlı olarak bir vtable'ı yoktur, çünkü bu sınıflar özellikle birçok yüksek performanslı uygulamanın iç döngüsünde kullanılır.


0

Temel sınıf üzerinde gerçekleştirilecek ve sanal olarak davranması gereken işlem sanal olmalıdır. Silme, temel sınıf arabirimi aracılığıyla polimorfik olarak gerçekleştirilebiliyorsa, sanal olarak davranması ve sanal olması gerekir.

Sınıftan türetmeyi düşünmüyorsanız, yıkıcının sanal olmasına gerek yoktur. Ve yapsanız bile , temel sınıf işaretçilerinin silinmesi gerekmiyorsa , korumalı sanal olmayan bir yıkıcı da aynı derecede iyidir .


-7

Performans cevabı, gerçek olma şansı olduğunu bildiğim tek cevap. Yıkıcılarınızı sanallaştırmadan kaldırmanın işleri gerçekten hızlandırdığını ölçtüyseniz ve bulduysanız, muhtemelen o sınıfta hızlanması gereken başka şeyler de vardır, ancak bu noktada daha önemli hususlar vardır. Bir gün birisi kodunuzun onlar için güzel bir temel sınıf sağlayacağını keşfedecek ve onlara bir haftalık işten tasarruf ettirecek. Kodunuzu temel olarak kullanmak yerine, o haftanın işini yaptıklarından, kodunuzu kopyalayıp yapıştırdıklarından emin olmalısınız. Hiç kimsenin sizden miras alamaması için bazı önemli yöntemlerinizi özel yaptığınızdan emin olmalısınız.


Polimorfizm kesinlikle işleri yavaşlatacaktır. Bunu çok biçimliliğe ihtiyacımız olduğu ve tercih etmemeyi tercih ettiğimiz bir durumla karşılaştırın, daha da yavaş olacaktır. Örnek: Kaynakları temizlemek için RTTI ve bir switch deyimi kullanarak temel sınıf yıkıcıdaki tüm mantığı uygularız.
Eylül

1
C ++ 'da, temel sınıf olarak kullanıma uygun olmadığını belgelediğiniz sınıflarınızdan miras almamı durdurmak sizin sorumluluğunuz değildir. Mirası dikkatli kullanmak benim sorumluluğum. Tabii ki ev stili rehberi aksini söylemediği sürece.
Steve Jessop

1
... yıkıcıyı sanal yapmak, sınıfın temel sınıf olarak doğru çalışacağı anlamına gelmez. Yani sanal olarak "sırf" olarak işaretlemek, bu değerlendirmeyi yapmak yerine kodumun bozduramayacağı bir çek yazmaktır.
Steve Jessop
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.