C ++ 'da özel sanal yöntem


125

Özel bir yöntemi C ++ 'da sanal yapmanın avantajı nedir?

Bunu açık kaynaklı bir C ++ projesinde fark ettim:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
Bence soru geriye doğru. Bir şeyi sanal yapmanın nedeni her zaman aynıdır: türetilmiş sınıfların onu geçersiz kılmasına izin vermek. Öyleyse soru şu olmalıdır: Sanal bir yöntemi özel yapmanın avantajı nedir? Cevap şu: her şeyi varsayılan olarak özel yapın. :-)
ShreevatsaR

1
@ShreevatsaR Ama kendi sorunuzu bile cevaplamadınız ......
Spencer

@ShreevatsaR Geriye doğru farklı bir şekilde kastettiğinizi düşünmüştüm: Sanal bir yöntemi özel değil yapmanın avantajı nedir ?
Peter - Monica'yı Yeniden

Yanıtlar:


116

Herb Sutter çok güzel anlatmıştır burada .

Yönerge # 2: Sanal işlevleri özel yapmayı tercih edin.

Bu, türetilmiş sınıfların, sanal işlevleri doğrudan türetilmiş sınıflar tarafından çağrılabilir hale getirerek (işlevler yalnızca korumalı olsaydı mümkün olacaktı) daha fazla açığa çıkarmadan, davranışı gerektiği gibi özelleştirmek için işlevi geçersiz kılar. Buradaki önemli nokta, özelleştirmeye izin veren sanal işlevlerin var olmasıdır; doğrudan türetilmiş sınıfların kodundan çağrılmaları gerekmedikçe, onları özel dışında hiçbir şey yapmaya gerek yoktur.


Cevabımdan tahmin edebileceğiniz gibi, Sutter'ın 3 numaralı kılavuzunun, 2 numaralı kılavuzu pencereden dışarı attığını düşünüyorum.
Spencer

66

Yöntem sanal ise, özel olsa bile türetilmiş sınıflar tarafından geçersiz kılınabilir. Sanal yöntem çağrıldığında, geçersiz kılınan sürüm çağrılacaktır.

(Cevabında Prasoon Saurav tarafından alıntılanan Herb Sutter'ın aksine, C ++ FAQ Lite özel sanallara karşı tavsiye ediyor , çünkü çoğunlukla insanların kafasını karıştırıyor.)


41
Görünüşe göre C ++ FAQ Lite tavsiyesini değiştirdi: " C ++ SSS eskiden özel sanallar yerine korumalı sanalların kullanılması öneriliyordu. Bununla birlikte, özel sanal yaklaşım artık acemilerin kafa karışıklığının daha az endişe verici olduğu kadar yaygın. "
Zack The İnsan

19
Ancak uzmanların kafa karışıklığı endişe kaynağı olmaya devam ediyor. Yanımda oturan dört C ++ profesyonelinden hiçbiri özel sanallardan haberdar değildi.
Newtonx

12

Bir sanal üyeyi özel ilan etmek için yapılan tüm çağrılara rağmen, argüman su tutmuyor. Sıklıkla, türetilmiş bir sınıfın bir sanal işlevi geçersiz kılması, temel sınıf sürümünü çağırmak zorunda kalacaktır. Bildirilirse yapamaz private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Sen sahip temel sınıf yöntemi ilan etmek protected.

Ardından, bir yorum yoluyla yöntemin geçersiz kılınması, ancak çağrılmaması gerektiğini belirtmenin çirkin bir yolunu almalısınız.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Böylece Herb Sutter'ın 3 numaralı kuralı ... Ama at yine de ahırdan çıktı.

protectedHerhangi bir türetilmiş sınıfın yazarına, korumalı iç kısımları anlamak ve doğru bir şekilde kullanmak için örtük olarak güvendiğiniz bir şey bildirdiğinizde , tıpkı bir friendbildirimin privateüyeler için daha derin bir güveni ifade ettiği gibi .

Bu güveni ihlal etmekten kötü davranışlar alan kullanıcılar (örneğin, belgelerinizi okumaya zahmet etmeyerek "bilgisiz" olarak etiketlenenler) suçlu sadece kendileri olur.

Güncelleme : Özel sanal işlevleri kullanarak bu şekilde sanal işlev uygulamalarını "zincirleyebileceğinizi" iddia eden bazı geri bildirimler aldım. Öyleyse, kesinlikle görmek isterim.

Kullandığım C ++ derleyicileri, türetilmiş bir sınıf uygulamasının özel bir temel sınıf uygulamasını çağırmasına kesinlikle izin vermez.

C ++ komitesi bu özel erişime izin vermek için "özel" i rahatlatırsa, tamamen özel sanal işlevler için olurum. Halen, at çalındıktan sonra ahır kapısını kilitlememiz tavsiye ediliyor.


3
Argümanınızı geçersiz buluyorum. Bir API geliştiricisi olarak, yanlış kullanımı zor bir arayüz için çabalamalı ve bunu yaparken başka bir geliştiriciyi kendi hatalarınız için ayarlamamalısınız. Örneğinizde yapmak istediğiniz şey, yalnızca özel sanal yöntemlerle uygulanabilir.
sigy

1
Ben öyle demedim. Ancak, özel bir temel sınıf işlevini çağırmanıza gerek kalmadan aynı etkiyi elde etmek için kodunuzu yeniden yapılandırabilirsiniz
sigy

3
Örneğinizde davranışını genişletmek istiyorsunuz set_data. Talimatlar m_data = ndata;ve cleanup();bu nedenle tüm uygulamalar için tutması gereken bir değişmez olarak düşünülebilir. Bu nedenle cleanup()sanal olmayan ve özel yapın. Sanal ve sınıfınızın uzantı noktası olan başka bir özel yönteme bir çağrı ekleyin. Artık türetilmiş sınıflarınızın tabanları çağırmasına gerek cleanup()yok, kodunuz temiz kalıyor ve arayüzünüzün yanlış kullanılması zor.
sigy

2
@sigy Bu sadece kale direklerini hareket ettirir. Miminal örneğin ötesine bakmanız gerekiyor. cleanup()Zincirdeki tüm s'leri çağırması gereken başka nesiller olduğunda , argüman dağılır. Yoksa zincirdeki her alt öğe için fazladan bir sanal işlev mi öneriyorsunuz? Ick. Herb Sutter bile 3. kılavuzunda bir boşluk olarak korunan sanal işlevlere izin verdi. Her neyse, gerçek bir kod olmadan beni asla ikna edemezsin.
Spencer

2
O zaman aynı fikirde
olmayalım

9

Bu kavramla ilk olarak Scott Meyers "Etkili C ++", Madde 35: Sanal işlevlere alternatifleri düşünün. İlgilenebilecek diğerleri için Scott Mayers'a başvurmak istedim.

Sanal Olmayan Arayüz deyimi aracılığıyla Şablon Yöntem Modelinin bir parçasıdır : halka açık yöntemler sanal değildir; bunun yerine, özel olan sanal yöntem çağrılarını sararlar. Temel sınıf daha sonra özel sanal işlev çağrısından önce ve sonra mantığı çalıştırabilir:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Bunun çok ilginç bir tasarım modeli olduğunu düşünüyorum ve eminim eklenen kontrolün ne kadar faydalı olduğunu görebilirsiniz.

  • Neden sanal işlevi yapmalı private? En iyi neden, zaten bir publicyüzleşme yöntemi sağlamış olmamızdır .
  • protectedYöntemi başka ilginç şeyler için kullanabilmem için neden basitçe yapmıyorum ? Sanırım bu her zaman tasarımınıza ve temel sınıfın nasıl uygun olduğuna inandığınıza bağlı olacaktır. Türetilmiş sınıf oluşturucunun gerekli mantığı uygulamaya odaklanması gerektiğini savunuyorum; diğer her şey zaten halledildi. Bir de kapsülleme meselesi var.

C ++ perspektifinden, sınıfınızdan arayamayacak olsanız bile özel bir sanal yöntemi geçersiz kılmak tamamen meşrudur. Bu, yukarıda açıklanan tasarımı destekler.


3

Bunları, türetilmiş sınıfların bir temel sınıf için "boşlukları doldurmasına" izin vermek için kullanıyorum, son kullanıcılara böyle bir delik açmadan. Örneğin, genel durum makinesinin yalnızca 2 / 3'ünü uygulayabilen ortak bir tabandan türetilen yüksek düzeyde durum bilgili nesnelerim var (türetilmiş sınıflar, bir şablon argümanına bağlı olarak kalan 1 / 3'ü sağlar ve taban, için bir şablon olamaz diğer sebepler).

Genel API'lerin birçoğunun doğru çalışmasını sağlamak için ortak temel sınıfa ihtiyacım var (değişken şablonlar kullanıyorum), ancak bu nesnenin doğaya çıkmasına izin veremem. Daha da kötüsü, kraterleri durum makinesinde - saf sanal işlevler biçiminde - herhangi bir yerde ancak "Özel" olarak bırakırsam, alt sınıflarından birinden türeyen zeki veya bilgisiz bir kullanıcının, kullanıcıların asla dokunmaması gereken yöntemleri geçersiz kılmasına izin veririm. Bu yüzden, durum makinesi 'beyinlerini' ÖZEL sanal işlevlere koydum. Ardından, temel sınıfın hemen alt öğeleri, sanal OLMAYAN geçersiz kılmalarındaki boşlukları doldurur ve kullanıcılar, sonuçtaki nesneleri güvenli bir şekilde kullanabilir veya durum makinesini karıştırmaktan endişe etmeden kendi türetilmiş sınıflarını oluşturabilir.

Herkese açık sanal yöntemlere sahip olmamanız gerektiği argümanına gelince, BS diyorum. Kullanıcılar, özel sanalları tıpkı genel olanlar kadar kolay bir şekilde geçersiz kılabilir - sonuçta yeni sınıflar tanımlıyorlar. Eğer halkın belirli bir API'yi değiştirmemesi gerekiyorsa, onu herkesin erişebileceği nesnelerde sanal yapmayın.

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.