Türetilmiş sınıflardaki işlevler için C ++ “sanal” anahtar sözcüğü. Bu gerekli mi?


221

Aşağıda verilen yapı tanımı ile ...

struct A {
    virtual void hello() = 0;
};

Yaklaşım # 1:

struct B : public A {
    virtual void hello() { ... }
};

Yaklaşım # 2:

struct B : public A {
    void hello() { ... }
};

Merhaba işlevini geçersiz kılmanın bu iki yolu arasında herhangi bir fark var mı?


65
C ++ 11'de, sanal bir yöntemi geçersiz kıldığınızı açıkça belirtmek için "void hello () geçersiz kılma {}" yazabilirsiniz. Bir temel sanal yöntem yoksa derleyici başarısız olur ve alt öğe sınıfına "sanal" yerleştirmekle aynı okunabilirliğe sahiptir.
ShadowChaser

Aslında, gcc'nin C ++ 11'inde, türetilmiş sınıfta void hello () geçersiz kılma {} yazma işlemi iyidir, çünkü temel sınıf hello () yönteminin sanal olduğunu belirtmiştir. Başka bir deyişle, türetilmiş sınıfta sanal sözcüğün kullanılması, yine de gcc / g ++ için gerekli / zorunlu değildir. (RPi 3'te gcc sürüm 4.9.2 kullanıyorum) Ancak sanal anahtar kelimeyi türetilmiş sınıfın yöntemine zaten dahil etmek iyi bir uygulamadır.
Will

Yanıtlar:


183

Onlar tamamen aynı. İlk yaklaşımın daha fazla yazım gerektirmesi ve potansiyel olarak daha net olması dışında aralarında hiçbir fark yoktur.


25
Bu doğrudur, ancak Mozilla C ++ Taşınabilirlik Kılavuzu her zaman sanal kullanmanızı önerir, çünkü "bazı derleyiciler" uyarılar vermez. Bu tür derleyicilere örnek vermemeleri çok kötü.
Sergei Tachenov

5
Bunu açıkça sanal olarak işaretlemenin, yıkıcıyı da sanal hale getirmenizi hatırlatacağını da ekleyeceğim.
lfalin

1
Bahsetmek gerekirse, aynı şey sanal yıkıcı
Atul

6
@SergeyTachenov, clifford'un kendi cevabına yaptığı açıklamaya göre , bu tür derleyicilere örnek armcc'dir.
Ruslan

4
@Rasmi, yeni taşınabilirlik kılavuzu burada , ancak şimdi overrideanahtar kelimeyi kullanmanızı önerir .
Sergei Tachenov

83

Bir işlevin 'sanallığı' dolaylı olarak yayılır, ancak kullandığım en az bir derleyici virtualanahtar kelime açıkça kullanılmazsa bir uyarı oluşturur , bu nedenle yalnızca derleyiciyi sessiz tutmak için kullanmak isteyebilirsiniz.

Tamamıyla stilistik bir bakış açısından, virtualanahtar kelime de dahil olmak üzere , işlevin sanal olduğu gerçeğini kullanıcıya açıkça 'tanıtır'. Bu, A'nın tanımını kontrol etmek zorunda kalmadan B sınıfı alt sınıfı olan herkes için önemli olacaktır. Derin sınıf hiyerarşileri için bu özellikle önem kazanıyor.


12
Bu hangi derleyici?
James McNellis

35
@James: armcc (ARM cihazları için ARM derleyicisi)
Clifford

55

virtualAnahtar kelime türetilmiş sınıfta gerekli değildir. İşte C ++ Taslak Standardından (N3337) (vurgu mayını) destekleyici belgeler:

10.3 Sanal işlevler

2 vfBir sınıfta Baseve bir sınıfta Deriveddoğrudan veya dolaylı olarak türetilmiş bir sanal üye işlevi , aynı ada sahip Basebir üye işlevi vf, parametre türü listesi (8.3.5), cv-yeterlilik ve ref-niteleyici ( veya aynı olmaması) Base::vfbildirilirse, o zaman Derived::vfsanaldır ( bu şekilde bildirilmiş olsun veya olmasın ) ve geçersiz kılar Base::vf.


5
Bu açık ara en iyi cevap.
Fantastik Bay Fox

33

Hayır, virtualtüretilmiş sınıfların sanal işlev geçersiz kılmaları üzerindeki anahtar sözcük gerekli değildir. Ancak ilgili bir tuzaktan bahsetmeye değer: sanal bir işlevi geçersiz kılma hatası.

Geçersiz kılma için başarısızlık bir türetilmiş sınıftaki sanal işlevi geçersiz niyetinde, ancak yeni ve farklı bir sanal işlevini bildirir, böylece imza bir hata yaparsanız oluşur. Bu işlev , temel sınıf işlevinin aşırı yüklenmesi olabilir veya ad olarak farklı olabilir. virtualTüretilmiş sınıf işlev bildiriminde anahtar sözcüğü kullansanız da kullanmasanız da , derleyici bir temel sınıftan bir işlevi geçersiz kılmayı amaçladığınızı söyleyemez.

Bununla birlikte, bu tuzak, C ++ 11 açık geçersiz kılma dili özelliği tarafından şükranla ele alınır , bu da kaynak kodunun bir üye işlevinin bir temel sınıf işlevini geçersiz kılmayı amaçladığını açıkça belirtmesini sağlar:

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

Derleyici bir derleme zamanı hatası verir ve programlama hatası hemen belli olur (belki de Derived'deki işlev floatargüman olarak a almış olmalıdır ).

Bakınız WP: C ++ 11 .


11

"Sanal" anahtar kelimeyi eklemek, okunabilirliği geliştirdiği için iyi bir uygulamadır, ancak gerekli değildir. Temel sınıfta sanal olarak bildirilen ve türetilmiş sınıflarda aynı imzayı taşıyan işlevler varsayılan olarak "sanal" olarak kabul edilir.


7

virtualTüretilmiş sınıfa yazdığınızda veya atladığınızda derleyici için bir fark yoktur .

Ancak bu bilgiyi almak için temel sınıfa bakmanız gerekir. Bu nedenle virtual, insana bu işlevin sanal olduğunu göstermek istiyorsanız , anahtar kelimeyi türetilmiş sınıfta eklemenizi tavsiye ederim .


2

Şablonlarınız olduğunda ve temel sınıf (lar) ı şablon parametreleri olarak almaya başladığınızda önemli bir fark vardır:

struct None {};

template<typename... Interfaces>
struct B : public Interfaces
{
    void hello() { ... }
};

struct A {
    virtual void hello() = 0;
};

template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
    b.hello();   // indirect, non-virtual call
}

void hello(const A& a)
{
    a.hello();   // Indirect virtual call, inlining is impossible in general
}

int main()
{
    B<None>  b;         // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
    B<None>* pb = &b;
    B<None>& rb = b;

    b.hello();          // direct call
    pb->hello();        // pb-relative non-virtual call (1 redirection)
    rb->hello();        // non-virtual call (1 redirection unless optimized out)
    t_hello(b);         // works as expected, one redirection
    // hello(b);        // compile-time error


    B<A>     ba;        // Ok, vtable generated, sizeof(b) >= sizeof(void*)
    B<None>* pba = &ba;
    B<None>& rba = ba;

    ba.hello();         // still can be a direct call, exact type of ba is deducible
    pba->hello();       // pba-relative virtual call (usually 3 redirections)
    rba->hello();       // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
    //t_hello(b);       // compile-time error (unless you add support for const A& in t_hello as well)
    hello(ba);
}

Bunun eğlenceli kısmı, daha sonra sınıfları tanımlamak için arayüz ve arayüz olmayan fonksiyonları tanımlayabilmenizdir. Bu, kütüphaneler arasındaki arayüzler için yararlıdır ( tek bir kütüphanenin standart tasarım süreci olarak buna güvenmeyin ). Tüm sınıflarınız için buna izin vermek size hiçbir maliyeti yoktur - isterseniz bir typedefşeye bile B yapabilirsiniz.

Bunu yaparsanız, kopyalama / taşıma yapıcılarını şablon olarak da bildirmek isteyebileceğinizi unutmayın: farklı arabirimlerden oluşturmaya izin vermek, farklı B<>türler arasında 'yayınlamanızı' sağlar .

Bu desteği eklemek gerekip gerekmediğini şüpheli var const A&içinde t_hello(). Bu yeniden yazmanın genel nedeni, çoğunlukla performans nedenleriyle, kalıtım temelli uzmanlıktan şablon temelli olana geçmektir. Eski arayüzü desteklemeye devam ederseniz, eski kullanımı zorlukla tespit edebilir (veya engelleyebilirsiniz).


1

virtualAnahtar kelime bunları geçersiz kılınabilir yapmak için bir temel sınıf işlevleri eklenmelidir. Örneğin, struct Atemel sınıftır. virtualtüretilmiş bir sınıfta bu işlevleri kullanmak için hiçbir şey ifade etmez. Ancak, türetilmiş sınıfınızın da bir temel sınıf olmasını ve bu işlevin geçersiz kılınmasını istiyorsanız, virtualoraya oraya koymanız gerekir .

struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};

Burada Cmiras alınır B, Btemel sınıf da değildir (aynı zamanda türetilmiş bir sınıftır) ve Ctüretilmiş sınıftır. Kalıtım şeması şöyle görünür:

A
^
|
B
^
|
C

Bu nedenle virtual, işlevleri olabilecek çocuklara sahip olabilecek temel sınıfların içine koymalısınız . virtualçocuklarınızın işlevlerinizi geçersiz kılmalarını sağlar. virtualTüretilmiş sınıfların içine işlevlerin önüne koymakla ilgili yanlış bir şey yoktur , ancak zorunlu değildir. Yine de önerilir, çünkü eğer birisi türetilmiş sınıfınızdan miras almak istiyorsa, geçersiz kılma yönteminin beklendiği gibi çalışmadığından memnun olmazlar.

Bu nedenle virtual, sınıfın temel sınıfın işlevlerini geçersiz kılması gereken herhangi bir çocuğa sahip olmayacağından emin değilseniz, kalıtımla ilgili tüm sınıflardaki işlevlerin önüne koyun . İyi uygulama.


0

Kesinlikle alt sınıf için Sanal anahtar sözcüğü ekleyeceğim, çünkü

  • ben. Okunabilirlik.
  • ii. Bu alt sınıf benim daha aşağı türetilmişse, daha ileri türetilmiş sınıfın yapıcısının bu sanal işlevi çağırmasını istemezsiniz.

1
Bence çocuk işlevini sanal olarak işaretlemeden, daha sonra çocuk sınıfından türeyen bir programcı, işlevin aslında sanal olduğunu fark edemeyebilir (çünkü asla temel sınıfa bakmadı) ve inşaat sırasında potansiyel olarak çağırabilir ( doğru olanı yapabilir veya yapmayabilir).
PfhorSlayer
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.