Bir sınıf için sanal yıkıcı ilan etmemek için iyi bir neden var mı ? Özellikle ne zaman bir tane yazmaktan kaçınmalısınız?
Bir sınıf için sanal yıkıcı ilan etmemek için iyi bir neden var mı ? Özellikle ne zaman bir tane yazmaktan kaçınmalısınız?
Yanıtlar:
Aşağıdakilerden herhangi biri doğru olduğunda sanal bir yıkıcı kullanmaya gerek yoktur:
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.
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 =default
etmek, 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
}
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.
delete
Sı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, B
aş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 delete
sı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.
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.
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.
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!"
Ç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.
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.
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.
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 .
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.