Fungsi virtual murni dengan implementasi

176

Pemahaman dasar saya adalah bahwa tidak ada implementasi untuk fungsi virtual murni, namun, saya diberitahu mungkin ada implementasi untuk fungsi virtual murni.

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

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

Apakah kode di atas OK?

Apa tujuan menjadikannya fungsi virtual murni dengan implementasi?

skydoor
sumber

Jawaban:

215

virtualFungsi murni harus diimplementasikan dalam tipe turunan yang akan langsung dipakai, namun tipe dasar masih dapat mendefinisikan implementasi. Kelas turunan dapat secara eksplisit memanggil implementasi kelas dasar (jika izin akses mengizinkannya) dengan menggunakan nama dengan cakupan penuh (dengan memanggil A::f()dalam contoh Anda - jika A::f()ada publicatau protected). Sesuatu seperti:

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();
    }

};

Kasus penggunaan yang dapat saya pikirkan di atas kepala saya adalah ketika ada perilaku default yang lebih atau kurang masuk akal, tetapi desainer kelas ingin bahwa perilaku semacam standar dipanggil hanya secara eksplisit. Ini juga bisa menjadi kasus yang Anda inginkan kelas turunan untuk selalu melakukan pekerjaan mereka sendiri tetapi juga dapat memanggil serangkaian fungsi umum.

Perhatikan bahwa meskipun diizinkan oleh bahasa, itu bukan sesuatu yang saya lihat umum digunakan (dan fakta bahwa hal itu dapat dilakukan tampaknya mengejutkan sebagian besar programmer C ++, bahkan yang berpengalaman).

Michael Burr
sumber
1
Anda lupa menambahkan mengapa itu mengejutkan programmer: itu karena definisi sebaris dilarang oleh standar. Definisi metode virtual murni harus deported. (baik dalam .inl atau .cpp untuk merujuk pada praktik penamaan file yang umum).
v.oddou
jadi metode pemanggilan ini sama dengan pemanggilan anggota metode statis. Semacam metode kelas di Jawa.
Sany Liew
2
"tidak umum digunakan" == praktik buruk? Saya mencari perilaku yang sama persis, mencoba menerapkan NVI. Dan NVI tampaknya merupakan praktik yang baik untuk saya.
Saskia
5
Perlu ditunjukkan bahwa membuat A :: f () murni berarti bahwa B harus mengimplementasikan f () (jika tidak B akan abstrak dan tidak dapat dibuktikan kebenarannya). Dan seperti yang ditunjukkan @MichaelBurr, menyediakan implementasi untuk A :: f () berarti bahwa B dapat menggunakannya untuk mendefinisikan f ().
fearless_fool
2
IIRC, Scot Meyer memiliki artikel yang bagus tentang kasus penggunaan pertanyaan ini di salah satu buku klasiknya "Lebih efektif C ++"
irsis
75

Agar jelas, Anda salah paham apa = 0; setelah fungsi virtual berarti.

= 0 berarti kelas turunan harus menyediakan implementasi, bukan kelas dasar tidak dapat menyediakan implementasi.

Dalam praktiknya, ketika Anda menandai fungsi virtual sebagai murni (= 0), ada sangat sedikit gunanya dalam memberikan definisi, karena definisi tersebut tidak akan pernah dipanggil kecuali seseorang secara eksplisit melakukannya melalui Base :: Function (...) atau jika Konstruktor kelas dasar memanggil fungsi virtual yang dimaksud.

Terry Mahaffey
sumber
9
Ini salah. Jika Anda memanggil fungsi virtual murni di konstruktor kelas virtual murni Anda, panggilan virtual murni akan dibuat. Dalam hal ini Anda sebaiknya memiliki implementasi.
rmn
@rmn, Ya, Anda benar tentang panggilan virtual dalam konstruktor. Saya memperbarui jawabannya. Semoga semua orang tahu untuk tidak melakukannya. :)
Terry Mahaffey
3
Bahkan, membuat panggilan dasar murni dari hasil konstruktor dalam perilaku yang ditentukan implementasi. Dalam VC ++, jumlah itu merupakan crash _purecall.
Ofek Shilon
@OfekShilon yang benar - Saya akan tergoda juga menyebutnya perilaku tidak terdefinisi dan kandidat untuk praktik buruk / refactoring kode (yaitu memanggil metode virtual di dalam konstruktor). Saya kira itu ada hubungannya dengan koherensi tabel virtual, yang mungkin tidak siap untuk rute ke tubuh implementasi yang benar.
teodron
1
Dalam konstruktor dan destruktor, fungsi virtual tidak virtual.
Jesper Juhl
20

Keuntungannya adalah ia memaksa tipe turunan untuk tetap menimpa metode tetapi juga menyediakan implementasi standar atau tambahan.

JaredPar
sumber
1
Mengapa saya ingin memaksakan jika ada implementasi standar? Kedengarannya seperti fungsi virtual normal. Jika itu hanya fungsi virtual normal, saya bisa menimpa dan jika tidak, maka implementasi default akan diberikan (implementasi basis).
StackExchange123
19

Jika Anda memiliki kode yang harus dieksekusi oleh kelas turunan, tetapi Anda tidak ingin kode itu dieksekusi secara langsung - dan Anda ingin memaksanya untuk ditimpa.

Kode Anda benar, meskipun semuanya ini bukan fitur yang sering digunakan, dan biasanya hanya terlihat ketika mencoba mendefinisikan destruktor virtual murni - dalam hal ini Anda harus menyediakan implementasi. Yang lucu adalah bahwa sekali Anda berasal dari kelas itu, Anda tidak perlu menimpa destruktor.

Oleh karena itu, penggunaan fungsi virtual murni yang masuk akal adalah menentukan destruktor virtual murni sebagai kata kunci "non-final".

Kode berikut ini ternyata benar:

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

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}
Kornel Kisielewicz
sumber
1
Penghancur kelas dasar selalu disebut pula, virtual atau tidak dan murni atau tidak; dengan fungsi lain Anda tidak dapat menjamin bahwa fungsi virtual utama akan memanggil implementasi kelas dasar apakah versi kelas dasar murni atau tidak.
CB Bailey
1
Kode itu salah. Anda harus mendefinisikan dtor di luar definisi kelas karena sintaks bahasa.
@Roger: terima kasih, itu benar-benar membantu saya - ini adalah kode yang saya gunakan, mengkompilasi dengan baik di bawah MSVC, tapi saya kira itu tidak akan portabel.
Kornel Kisielewicz
4

Ya ini benar. Dalam contoh Anda, kelas yang berasal dari A mewarisi antarmuka f () dan implementasi default. Tapi Anda memaksa kelas turunan untuk mengimplementasikan metode f () (bahkan jika itu hanya untuk memanggil implementasi default yang disediakan oleh A).

Scott Meyers membahas hal ini dalam C ++ Efektif (Edisi ke-2) Item # 36 Bedakan antara pewarisan antarmuka dan pewarisan implementasi. Nomor item mungkin telah berubah di edisi terbaru.

Yukiko
sumber
4

Fungsi virtual murni dengan atau tanpa tubuh hanya berarti bahwa tipe turunan harus menyediakan implementasi mereka sendiri.

Badan fungsi virtual murni di kelas dasar berguna jika kelas turunan Anda ingin memanggil implementasi kelas dasar Anda.

Brian R. Bondy
sumber
2

The 'virtual void foo () = 0;' sintaks tidak berarti Anda tidak dapat mengimplementasikan foo () di kelas saat ini, Anda bisa. Ini juga tidak berarti Anda harus mengimplementasikannya di kelas turunan . Sebelum Anda menampar saya, mari kita amati Masalah Intan: (Kode implisit, ingatlah).

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;
}

Sekarang, permohonan obj-> foo () akan menghasilkan B :: foo () dan kemudian C :: bar ().

Anda lihat ... metode virtual murni tidak harus diimplementasikan di kelas turunan (foo () tidak memiliki implementasi di kelas C - kompiler akan dikompilasi) Di C ++ ada banyak celah.

Semoga saya bisa membantu :-)

Nir Hedvat
sumber
5
Itu tidak perlu diimplementasikan di SEMUA kelas turunan, tetapi HARUS memiliki implementasi di semua kelas turunan yang ingin Anda instantiate. Anda tidak dapat membuat instance objek tipe Cdalam contoh Anda. Anda bisa instantiate objek tipe Dkarena mendapat implementasi foodari B.
YoungJohn
0

Salah satu kasus penggunaan penting memiliki metode virtual murni dengan badan implementasi , adalah ketika Anda ingin memiliki kelas abstrak, tetapi Anda tidak memiliki metode yang tepat di kelas untuk menjadikannya virtual murni. Dalam hal ini, Anda dapat membuat destruktor kelas virtual murni dan meletakkan implementasi yang Anda inginkan (bahkan sebuah badan kosong) untuk itu. Sebagai contoh:

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

Foo::~Foo()
{
}

Teknik ini, membuat Fookelas abstrak dan sebagai hasilnya tidak mungkin untuk membuat instance kelas secara langsung. Pada saat yang sama Anda belum menambahkan metode virtual murni tambahan untuk membuat Fooabstrak kelas.

Gupta
sumber