Uygulama ile saf sanal işlev


176

Temel anlayışım, saf bir sanal fonksiyon için bir uygulama olmadığı, ancak saf sanal fonksiyon için bir uygulama olabileceği söylendi.

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

Kod tamam mı?

Bir uygulama ile onu saf bir sanal işlev haline getirmenin amacı nedir?

Yanıtlar:


215

Saf bir virtualişlev, doğrudan somutlaştırılacak türetilmiş bir türde uygulanmalıdır, ancak taban türü yine de bir uygulamayı tanımlayabilir. (Çağrılarak tam kapsamlı adı kullanarak (erişim izinleri buna izin varsa) Türetilmiş sınıf açıkça temel sınıf uygulaması çağırabilir A::f()- eğer örnekte A::f()olduğu publicveya protected). Gibi bir şey:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

Başımın en üstünde düşünebileceğim kullanım örneği, az ya da çok makul bir varsayılan davranış olduğunda, ancak sınıf tasarımcısı varsayılan tür davranışların sadece açıkça çağrılmasını istiyor. Ayrıca, türetilmiş sınıfların her zaman kendi çalışmalarını gerçekleştirmelerini istediğiniz, ancak ortak bir işlevsellik seti çağırabileceğiniz durum da olabilir.

Dil tarafından izin verilse de, yaygın olarak kullandığım bir şey olmadığını unutmayın (ve yapılabileceği gerçeği, çoğu C ++ programcısını, hatta deneyimli olanları şaşırtıyor gibi görünüyor).


1
Neden programcıları şaşırttığını eklemeyi unuttunuz: çünkü satır içi tanım standart olarak yasaklanmıştır. Saf sanal yöntem tanımları olmalıdır deported. (ortak dosya adlandırma uygulamalarına başvurmak için .inl veya .cpp dosyalarında).
v.oddou

böylece bu çağrı yöntemi çağrı statik yöntem üye ile aynıdır. Java'da bir çeşit sınıf yöntemi.
Sany Liew

2
"yaygın olarak kullanılmıyor" == kötü uygulama? NVI'yı uygulamaya çalışırken aynı davranışı arıyordum. Ve NVI benim için iyi bir uygulama gibi görünüyor.
Saskia

5
A :: f () saflaştırmanın B'nin f () uygulaması gerektiği anlamına geldiğini belirtmek gerekir (aksi takdirde B soyut ve örneksiz olacaktır). Ve @MichaelBurr A için bir uygulama :: r () B olduğu anlamına gelir sağlayan işaret ettiği gibi olabilir f tanımlamak için kullanmak ().
fearless_fool

2
IIRC, Scot Meyer, klasik kitabı "Daha etkili C ++" dan birinde bu sorunun kullanım durumu hakkında mükemmel bir makaleye sahiptir
irsis

75

Açıkça söylemek gerekirse, ne = 0; sonra sanal bir işlev anlamına gelir.

= 0, türetilmiş sınıfların bir uygulama sağlaması gerektiği anlamına gelir, temel sınıf bir uygulama sağlayamaz.

Pratikte, bir sanal işlevi saf (= 0) olarak işaretlediğinizde, bir tanım sağlamanın çok az bir anlamı vardır, çünkü birisi Base :: Function (...) aracılığıyla açıkça yapmadıkça veya Temel sınıf yapıcısı söz konusu sanal işlevi çağırır.


9
Bu yanlış. Bu saf sanal işlevi saf sanal sınıfınızın yapıcısında çağırırsanız, saf bir sanal çağrı yapılır. Bu durumda bir uygulamanız daha iyi olur.
rmn

@rmn, Evet, yapıcılardaki sanal aramalar konusunda haklısınız. Cevabı güncelledim. Umarım herkes bunu yapmamayı bilir. :)
Terry Mahaffey

3
Aslında, bir kurucudan saf bir çağrı yapmak, uygulama tanımlı davranışla sonuçlanır. VC ++ 'da, bu _purecall kilitlenmesine karşılık gelir.
Ofek Shilon

@OfekShilon doğru - ben de tanımsız davranış ve kötü uygulama / kod yeniden düzenleme için bir aday (yani yapıcı içinde sanal yöntemleri çağırmak) olarak adlandırmak cazip olurdu. Sanırım doğru uygulama gövdesine yönlendirilmeye hazır olmayan sanal tablo tutarlılığıyla ilgili.
teodron

1
Yapıcılarda ve yıkıcılarda sanal işlevler sanal değildir .
Jesper Juhl

20

Bunun avantajı, türetilmiş türleri yine de yöntemi geçersiz kılmaya zorlaması, ancak varsayılan veya ek bir uygulama sağlamasıdır.


1
Varsayılan bir uygulama varsa neden zorlamak isteyeyim? Kulağa normal sanal fonksiyonlar gibi geliyor. Sadece normal bir sanal işlev olsaydı, ya geçersiz kılabilirim ve eğer yapmazsam, varsayılan bir uygulama sağlanacak (tabanın uygulaması).
StackExchange123

19

Türetici sınıf tarafından yürütülmesi gereken bir kodunuz varsa, ancak bunun doğrudan yürütülmesini istemiyorsanız ve bunu geçersiz kılınmaya zorlamak istiyorsanız.

Kodunuz doğrudur, ancak tüm bunlar sıkça kullanılan bir özellik değildir ve genellikle yalnızca saf bir sanal yıkıcı tanımlamaya çalışırken görülür - bu durumda bir uygulama sağlamanız gerekir . Komik olan şey, bu sınıftan türettikten sonra yıkıcıyı geçersiz kılmanıza gerek olmamasıdır.

Bu nedenle, saf sanal işlevlerin mantıklı bir kullanımı, "nihai olmayan" bir anahtar kelime olarak saf bir sanal yıkıcı belirtmektir.

Aşağıdaki kod şaşırtıcı derecede doğrudur:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
Temel sınıf yıkıcılar her zaman sanal ya da sanal olarak adlandırılır ve saf ya da çağrılmaz; diğer işlevlerle, temel sınıf sürümü saf olsun ya da olmasın, geçersiz kılan bir sanal işlevin temel sınıf uygulamasını çağıracağını garanti edemezsiniz.
CB Bailey

1
Bu kod yanlış. Dilin sözdizimi tuhaflığı nedeniyle dtor'u sınıf tanımının dışında tanımlamanız gerekir.

@Roger: teşekkürler, bu gerçekten bana yardımcı oldu - bu kullandığım kod, MSVC altında iyi derler, ama sanırım taşınabilir olmaz.
Kornel Kisielewicz


4

Evet bu doğru. Örneğin, A'dan türeyen sınıflar hem f () arabirimini hem de varsayılan bir uygulamayı devralır. Ancak türetilmiş sınıfları f () yöntemini uygulamaya zorlarsınız (yalnızca A tarafından sağlanan varsayılan uygulamayı çağırmak olsa bile).

Scott Meyers bunu Etkili C ++ (2. Baskı) 'da tartışıyor Madde # 36 Arayüzün mirası ile uygulamanın mirası arasında ayrım yapın. Öğe numarası en son sürümde değişmiş olabilir.


4

Bedeni olan veya olmayan saf sanal işlevler, türetilen türlerin kendi uygulamalarını sağlamaları gerektiği anlamına gelir.

Temel sınıftaki saf sanal işlev gövdeleri, türetilmiş sınıflarınız temel sınıf uygulamanızı çağırmak istiyorsa yararlıdır.


2

'Sanal boşluk foo () = 0;' sözdizimi geçerli sınıfta foo () uygulayamayacağınız anlamına gelmez. Ayrıca, türetilmiş sınıflarda uygulamanız gerektiği anlamına gelmez . Beni tokatlamadan önce, Pırlanta Sorunu'na bakalım: (Örtük kod, dikkat et).

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

Şimdi, obj-> foo () çağrısı B :: foo () ve ardından C :: bar () ile sonuçlanacaktır.

Gördüğünüz gibi ... saf sanal yöntemlerin türetilmiş sınıflarda uygulanması gerekmez (foo () C sınıfında bir uygulaması yoktur - derleyici derleyecektir) C ++ 'da çok boşluk vardır.

Umarım yardımcı olabilirim :-)


5
TÜM türetilmiş sınıflarda uygulanması gerekmez, ancak türetmek istediğiniz tüm türetilmiş sınıflarda bir uygulaması olması GEREKİR. Örneğinizde bir tür nesnesini başlatamazsınız C. Sen türde bir nesne örneğini Dbunun uygulanmasını alır çünkü foogelen B.
YoungJohn

0

Bir uygulama gövdesi ile saf bir sanal yönteme sahip olmanın önemli bir kullanım örneği, soyut bir sınıfa sahip olmak istediğinizdir, ancak sınıfta onu saf sanal yapmak için uygun yöntemleriniz yoktur. Bu durumda, sınıfın yıkıcısını saf sanal hale getirebilir ve bunun için istediğiniz uygulamayı (boş bir gövde bile) koyabilirsiniz. Örnek olarak:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

Bu teknik, Foosınıfı soyutlaştırır ve sonuç olarak sınıfı doğrudan başlatmayı imkansız hale getirir . Aynı zamanda Foosınıfı soyut yapmak için ek bir saf sanal yöntem eklemediniz .

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.