Özel bir saf sanal işlevin anlamı nedir?


139

Bir başlık dosyasında aşağıdaki kodla karşılaştım:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Bana göre bu, ya Enginesınıftan ya da ondan türetilmiş bir sınıfın, bu saf sanal işlevler için uygulamayı sağlaması gerektiği anlamına gelir . Ancak, türetilmiş sınıfların onları yeniden uygulamak için bu özel işlevlere erişebileceğini düşünmedim - neden sanal hale getirmeliyim?

Yanıtlar:


209

Konudaki soru oldukça yaygın bir karışıklık olduğunu gösteriyor. Karışıklık yeterince yaygın, C ++ SSS'nin özel sanalları kullanmaya karşı uzun zamandır savunması, çünkü karışıklık kötü bir şey gibi görünüyordu.

İlk önce karışıklıktan kurtulmak için: Evet, türetilmiş sınıflarda özel sanal işlevler geçersiz kılınabilir. Türetilmiş sınıfların yöntemleri, temel sınıftan sanal işlevleri çağıramaz, ancak onlar için kendi uygulamalarını sağlayabilirler. Temel sınıfta genel sanal olmayan arabirime ve türetilmiş sınıflarda özelleştirilebilen özel bir uygulamaya sahip Herb Sutter'e göre, "arabirimin belirtiminin, uygulamanın özelleştirilebilir davranışının belirtiminden daha iyi ayrılmasına" olanak tanır. "Sanallık" makalesinde daha fazla bilgi bulabilirsiniz .

Bununla birlikte, sunduğunuz kodda, bence daha fazla ilgiyi hak eden daha ilginç bir şey var. Ortak arabirim, aşırı yüklenmiş sanal olmayan işlevlerden oluşur ve bu işlevler, genel olmayan, aşırı yüklenmemiş sanal işlevleri çağırır. C ++ dünyasında her zamanki gibi bir deyim, bir adı var ve elbette faydalı. Adı (sürpriz, sürpriz!)

"Herkese Açık Aşırı Yüklenen Sanal Olmayanlar Korumalı Aşırı Yüklenmemiş Sanalları Arayın"

Gizleme kuralını düzgün bir şekilde yönetmeye yardımcı olur . Burada daha fazla bilgi edinebilirsiniz , ancak kısaca açıklamaya çalışacağım.

EngineSınıfın sanal işlevlerinin de onun arayüzü olduğunu ve saf sanal olmayan aşırı yüklenmiş bir dizi işlev olduğunu hayal edin . Eğer saf sanal olsaydı, yine de aşağıda açıklananla aynı problemle karşılaşabilir, ancak sınıf hiyerarşisinde daha düşük olabilir.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Şimdi, türetilmiş bir sınıf oluşturmak istediğinizi ve yalnızca yöntem için iki ints bağımsız değişken olarak alan yeni bir uygulama sağlamanız gerektiğini varsayalım.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Kullanım bildirimini türetilmiş sınıfa koymayı (veya ikinci aşırı yüklemeyi yeniden tanımlamayı) unutursanız, aşağıdaki senaryoda sorun yaşayabilirsiniz.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

EngineÜyelerin gizlenmesini engellemediyseniz , ifade:

myV8->SetState(5, true);

çağırır void SetState( int var, int val )dönüştürme, türetilmiş sınıftan trueiçin int.

Arayüz sanal değilse ve sanal uygulama herkese açık değilse, örneğin exmaple'ınızda, türetilmiş sınıfın yazarının düşünmesi gereken daha az sorunu vardır ve sadece yazabilir

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

sanal işlevin neden özel olması gerekir? Herkese açık olabilir mi?
Zengin

Herb Sutter'ın "Sanallık" makalesinde verilen ilkelerin bugün hala geçerli olup olmadığını merak ediyorum.
nurabha

@Rich Yapabilirsiniz, ancak herkese açık hale getirerek, niyetlerini daha net bir şekilde aktarabilirsiniz. İlk olarak, arayüzü herkese açık hale getirmek ve uygulamayı herkese açık hale getirmek istemiyorsanız, endişelerin ayrıldığını gösterir. İkincisi, sınıfların devralınmasının temel uygulamaları çağırabilmesini istiyorsanız, onları korumalı olarak bildirebilirsiniz; sadece temel uygulamaları çağırmadan kendi uygulamalarını sağlamalarını istiyorsanız, onları özel yaparsınız.
Dan

43

Özel saf sanal işlev, Sanal olmayan arayüz deyiminin temelidir (Tamam, her zaman saf sanal değil, yine de sanaldır). Tabii ki, bu başka şeyler için de kullanılıyor, ancak bunu en yararlı için buluyorum (: İki kelimeyle: bir kamu işlevinde, başlangıçta bazı ortak şeyler (günlük kaydı, istatistikler vb.) fonksiyonun sonunda ve sonra "ortada" bu özel sanal işlevi çağırmak için, bu türetilmiş sınıf için farklı olacaktır.

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Saf sanal - türetilmiş sınıfları onu uygulamakla yükümlüdür.

EDIT : Bununla ilgili daha fazla bilgi: Wikipedia :: NVI-idiom


17

Birincisi, bu türetilmiş bir sınıfın temel sınıfın (saf sanal işlev bildirimini içeren) çağırabileceği bir işlevi uygulamasını sağlar.


5
O sadece taban sınıfı çağırabilirsiniz!
underscore_d

4

EDIT: Geçersiz kılma yeteneği ve erişim / çağırma yeteneği hakkında açıklayıcı ifadeler.

Bu özel işlevleri geçersiz kılabilir. Örneğin, aşağıdaki örnek örnekler çalışır ( EDIT: türetilmiş sınıf yöntemini özel yaptı ve main()tasarım deseninin kullanım amacını daha iyi göstermek için türetilmiş sınıf yöntemi çağrısını bırakın . ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualkodunuzdakiler gibi bir temel sınıftaki yöntemler, genellikle Şablon Yöntemi tasarım desenini uygulamak için kullanılır . Bu tasarım deseni, temel sınıftaki kodu değiştirmeden temel sınıftaki bir algoritmanın davranışını değiştirmeye izin verir. Temel sınıf yöntemlerinin bir temel sınıf işaretçisi aracılığıyla çağrıldığı yukarıdaki kod, Şablon Yöntemi deseninin basit bir örneğidir.


Anlıyorum, ama türetilmiş sınıflar yine de bir çeşit erişime sahipse, neden onları özel hale getirmeye çalışıyorsunuz?
BeeBand

@BeeBand: Kullanıcı, genel türetilmiş sınıf sanal yöntem geçersiz kılmalarına erişebilir, ancak temel sınıf olanlarına erişemez. Bu durumda türetilmiş sınıf yazarı sanal yöntem geçersiz kılmalarını da özel tutabilir. Aslında bunu vurgulamak için yukarıdaki örnek kodda değişiklik yapacağım. Her iki durumda da, her zaman genel olarak miras alabilir ve özel temel sınıf sanal yöntemlerini geçersiz kılabilirler, ancak yine de yalnızca kendi türetilmiş sınıf sanal yöntemlerine erişebilirlerdi. Geçersiz kılma ve erişim / çağırma arasında bir ayrım yaptığımı unutmayın.
Void

çünkü yanılıyorsun. sınıflar arasındaki miras görünürlüğü Engineve DerivedEngineneyi DerivedEnginegeçersiz kılabileceği veya yapamayacağı (veya bu konuya erişim) ile hiçbir ilgisi yoktur .
wilhelmtell

@wilhelmtell: sigh Elbette haklısın . Cevabımı buna göre güncelleyeceğim.
Void

3

Özel sanal yöntem, verilen işlevi geçersiz kılabilecek türetilmiş sınıfların sayısını sınırlamak için kullanılır. Özel sanal yöntemi geçersiz kılması gereken türetilmiş sınıfların temel sınıfın bir arkadaşı olması gerekir.

DevX.com ile ilgili kısa bir açıklama bulunabilir .


DÜZENLE Şablon Yöntem Kalıbı'nda özel bir sanal yöntem etkin bir şekilde kullanılır . Türetilmiş sınıflar özel sanal yöntemi geçersiz kılabilir, ancak türetilmiş sınıflar temel sınıf özel sanal yöntemini (örnekte SetStateBoolve SetStateInt) çağıramaz . Yalnızca temel sınıf etkin bir şekilde kendi özel sanal yöntemini çağırabilir ( Yalnızca türetilmiş sınıfların bir sanal işlevin temel uygulamasını çağırması gerekiyorsa, sanal işlevi korumalı hale getirin ).

İlginç bir makale hakkında bulunabilir Virtuality .


2
@ Gentleman ... hmmm Colin D Bennett'in yorumuna ilerleyin. "Özel bir sanal işlev türetilmiş sınıflar tarafından geçersiz kılınabilir, ancak yalnızca temel sınıf içinden çağrılabilir" diye düşünüyor. @Michael Goldshteyn de böyle düşünüyor.
BeeBand

Sanırım özel bir sınıfın türetilmiş sınıfı tarafından görülememesi ilkesini unutmuşsunuzdur. OOP kuralları budur ve OOP olan tüm diller için geçerlidir. Türetilmiş bir sınıfın temel sınıf özel sanal yöntemini uygulaması için, temel sınıftan biri olması gerekir friend. Qt, XML DOM belge modellerini uygularken aynı yaklaşımı benimsemiştir.
Buhake Sindi

@ Gentleman: Hayır, unutmadım. Yorumumda bir yazım hatası yaptım. Bunun yerine "temel sınıf yöntemlerine erişim" yazmış olmalıyım "temel sınıf yöntemlerini geçersiz kılabilir". Türetilmiş sınıf, bu temel sınıf yöntemine erişemese bile, özel sanal temel sınıf yöntemini kesinlikle geçersiz kılabilir. Belirttiğiniz DevX.com makalesi yanlıştı (genel miras). Cevabımdaki kodu deneyin. Özel sanal temel sınıf yöntemine rağmen, türetilmiş sınıf bunu geçersiz kılabilir. Özel bir sanal temel sınıf yöntemini geçersiz kılma yeteneğini, onu çağırma yeteneğiyle karıştırmayalım.
Void

@ Gentleman: @ wilhelmtell cevabım / yorumumdaki hatayı gösterdi. Temel sınıf yönteminin türetilmiş sınıf erişilebilirliğini etkileyen kalıtım hakkındaki iddiam kapalı. Cevabınıza rahatsız edici yorumu kaldırdım.
Void

@Void, bir türetilmiş bir sınıf görüyoruz olabilir temel sınıf sanal özel yöntemi geçersiz ama kullanamaz. Yani, bu aslında bir Şablon Yöntemi örüntüsüdür.
Buhake Sindi

0

TL; DR yanıtı:

Başka bir kapsülleme düzeyi gibi davranabilirsiniz - korumalı ve özel arasında bir yerde : çocuk sınıfından çağıramazsınız, ancak geçersiz kılabilirsiniz.

Şablon Yöntemi tasarım deseni uygulanırken kullanışlıdır . Korumalı kullanabilirsiniz , ancak sanal ile birlikte özel , daha iyi kapsülleme nedeniyle daha iyi bir seçim olarak kabul edilebilir.

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.