Apa gunanya fungsi virtual murni pribadi?

139

Saya menemukan kode berikut dalam file header:

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

Bagi saya, ini menyiratkan bahwa Enginekelas atau kelas yang diturunkan darinya, harus menyediakan implementasi untuk fungsi virtual murni tersebut. Tapi saya tidak berpikir kelas turunan dapat memiliki akses ke fungsi-fungsi pribadi untuk mengimplementasikannya - jadi mengapa membuatnya virtual?

BeeBand
sumber

Jawaban:

209

Pertanyaan dalam topik ini menunjukkan kebingungan yang cukup umum. Kebingungan cukup umum, bahwa C ++ FAQ menganjurkan untuk tidak menggunakan virtual pribadi, untuk waktu yang lama, karena kebingungan tampaknya menjadi hal yang buruk.

Jadi untuk menghilangkan kebingungan terlebih dahulu: Ya, fungsi virtual pribadi dapat ditimpa dalam kelas turunan. Metode kelas turunan tidak dapat memanggil fungsi virtual dari kelas dasar, tetapi mereka dapat memberikan implementasi sendiri untuk mereka. Menurut Herb Sutter, memiliki antarmuka non-virtual publik di kelas dasar dan implementasi pribadi yang dapat dikustomisasi di kelas turunan, memungkinkan untuk lebih baik "pemisahan spesifikasi antarmuka dari spesifikasi perilaku yang dapat disesuaikan implementasi". Anda dapat membaca lebih lanjut tentang itu di artikelnya "Virtualitas" .

Namun ada satu hal lagi yang menarik dalam kode yang Anda presentasikan, yang patut mendapat perhatian lebih, menurut saya. Antarmuka publik terdiri dari serangkaian fungsi non-virtual yang kelebihan beban dan fungsi-fungsi tersebut memanggil fungsi virtual non-publik, non-kelebihan beban. Seperti biasa di dunia C ++ itu adalah idiom, ia memiliki nama dan tentu saja itu berguna. Namanya (kejutan, kejutan!)

"Non-Virtual Call Overloaded Publik Dilindungi Virtual Non-Overloaded"

Ini membantu untuk mengelola aturan persembunyian dengan benar . Anda dapat membaca lebih lanjut tentang ini di sini , tetapi saya akan mencoba menjelaskannya segera.

Bayangkan, bahwa fungsi virtual dari Enginekelas juga merupakan antarmuka dan itu adalah satu set fungsi kelebihan beban yang bukan virtual murni. Jika mereka murni virtual, orang masih bisa menghadapi masalah yang sama, seperti yang dijelaskan di bawah ini, tetapi lebih rendah dalam hirarki kelas.

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

Sekarang mari kita asumsikan Anda ingin membuat kelas turunan dan Anda perlu menyediakan implementasi baru hanya untuk metode ini, yang menggunakan dua int sebagai argumen.

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

Jika Anda lupa untuk menempatkan deklarasi penggunaan di kelas turunan (atau untuk mendefinisikan kembali kelebihan kedua), Anda bisa mendapat masalah dalam skenario di bawah ini.

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

Jika Anda tidak mencegah persembunyian Engineanggota, pernyataan:

myV8->SetState(5, true);

akan memanggil void SetState( int var, int val )dari kelas turunan, mengubahnya truemenjadi int.

Jika antarmuka bukan virtual dan implementasi virtual adalah non-publik, seperti di exmaple Anda, penulis kelas turunan memiliki satu masalah yang kurang untuk dipikirkan dan hanya dapat menulis

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
Maciej Hehl
sumber
mengapa fungsi virtual harus bersifat pribadi? Bisakah ini untuk umum?
Kaya
Saya ingin tahu apakah pedoman yang diberikan oleh Herb Sutter dalam artikelnya "Virtualitas" masih berlaku hari ini?
nurabha
@Rich Anda bisa, tetapi dengan membuatnya non-publik, Anda dapat menyampaikan maksud mereka dengan lebih jelas. Pertama, ini menunjukkan pemisahan kekhawatiran jika Anda tetap membuat antarmuka publik dan implementasi non-publik. Kedua, jika Anda ingin mewarisi kelas agar dapat memanggil implementasi basis, Anda dapat mendeklarasikannya terproteksi; jika Anda hanya ingin mereka memberikan implementasi mereka sendiri tanpa memanggil yang dasar, Anda menjadikannya pribadi.
Dan
43

Fungsi virtual murni privat adalah dasar dari idiom antarmuka Non-virtual (OK, itu tidak sepenuhnya virtual murni , tetapi masih virtual di sana). Tentu saja, ini digunakan untuk hal-hal lain juga, tetapi saya menemukan ini untuk yang paling berguna (: Dalam dua kata: dalam fungsi publik, Anda bisa meletakkan beberapa hal umum (seperti logging, statistik, dll.) Di awal dan di akhir fungsi dan kemudian, "di tengah" untuk memanggil fungsi virtual pribadi ini, yang akan berbeda untuk kelas turunan tertentu. Sesuatu seperti:

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()
{
    // ..
}

Virtual murni - hanya mewajibkan kelas turunan untuk mengimplementasikannya.

EDIT : Lebih lanjut tentang ini: Wikipedia :: NVI-idiom

Kiril Kirov
sumber
17

Yah, untuk satu, ini akan memungkinkan kelas turunan untuk mengimplementasikan fungsi yang kelas dasar (berisi deklarasi fungsi virtual murni) dapat memanggil.

Michael Goldshteyn
sumber
5
bahwa hanya kelas dasar yang bisa memanggil!
underscore_d
4

EDIT: Pernyataan yang diklarifikasi tentang kemampuan menimpa dan kemampuan untuk mengakses / memohon.

Ini akan dapat mengesampingkan fungsi-fungsi pribadi tersebut. Sebagai contoh, karya contoh yang dibuat berikut ini ( EDIT: membuat metode kelas turunan pribadi, dan menjatuhkan pemanggilan metode kelas turunan main()untuk lebih menunjukkan maksud pola desain yang digunakan. ):

#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 virtualmetode di kelas dasar seperti yang ada di kode Anda biasanya digunakan untuk menerapkan pola desain Metode Templat . Pola desain itu memungkinkan seseorang untuk mengubah perilaku suatu algoritma di kelas dasar tanpa mengubah kode di kelas dasar. Kode di atas di mana metode kelas dasar dipanggil melalui pointer kelas dasar adalah contoh sederhana dari pola Metode Templat.

Kosong
sumber
Begitu, tetapi jika kelas turunan memiliki semacam akses, mengapa repot-repot menjadikannya pribadi?
BeeBand
@BeeBand: Pengguna akan memiliki akses ke metode virtual kelas turunan publik, tetapi tidak akan memiliki akses ke yang kelas dasar. Penulis kelas turunan dalam kasus ini dapat menjaga metode virtual juga menimpa pribadi. Bahkan saya akan membuat perubahan pada kode sampel di atas untuk menekankan itu. Either way, mereka selalu bisa mewarisi secara publik dan menimpa metode virtual kelas dasar privat, tetapi mereka masih hanya memiliki akses ke metode virtual kelas turunan mereka sendiri. Perhatikan bahwa saya membuat perbedaan antara menimpa dan akses / doa.
Batal
karena kamu salah. visibilitas warisan antara kelas Enginedan DerivedEnginetidak ada hubungannya dengan apa yang DerivedEnginebisa atau tidak bisa menimpa (atau akses, dalam hal ini).
wilhelmtell
@wilhelmtell: sigh Anda tentu saja benar. Saya akan memperbarui jawaban saya sesuai.
Batal
3

Metode virtual privat digunakan untuk membatasi jumlah kelas turunan yang dapat mengesampingkan fungsi yang diberikan. Kelas turunan yang harus mengganti metode virtual pribadi harus menjadi teman kelas dasar.

Penjelasan singkat dapat ditemukan dari DevX.com .


EDIT Metode virtual pribadi secara efektif digunakan dalam Pola Metode Template . Kelas turunan dapat mengesampingkan metode virtual pribadi tetapi kelas turunan tidak dapat memanggilnya metode virtual pribadi kelas dasar (dalam contoh Anda, SetStateBooldan SetStateInt). Hanya kelas dasar yang dapat secara efektif memanggil metode virtual privatnya ( Hanya jika kelas turunan perlu memohon implementasi dasar dari fungsi virtual, buatlah fungsi virtual terlindungi ).

Artikel menarik dapat ditemukan tentang Virtualitas .

Buhake Sindi
sumber
2
@ Gentleman ... hmmm gulir ke bawah ke komentar Colin D Bennett. Dia tampaknya berpikir bahwa "Fungsi virtual pribadi dapat ditimpa oleh kelas turunan, tetapi hanya dapat dipanggil dari dalam kelas dasar.". @Michael Goldshteyn juga berpikir seperti itu juga.
BeeBand
Saya kira Anda sudah lupa prinsip bahwa kelas berbasis privat tidak dapat dilihat oleh kelas turunannya. Itu adalah aturan OOP dan itu berlaku pada semua bahasa yang OOP. Agar kelas turunan untuk mengimplementasikan metode virtual privat kelas dasarnya, ia harus menjadi friendkelas dasar. Qt telah mengambil pendekatan yang sama ketika mereka menerapkan model dokumen XML DOM mereka.
Buhake Sindi
@ Gentleman: Tidak, saya belum lupa. Saya salah ketik dalam komentar saya. Alih-alih "akses ke metode kelas dasar" Saya seharusnya menulis "dapat menimpa metode kelas dasar". Kelas turunan tentu saja dapat menimpa metode kelas dasar virtual pribadi bahkan jika tidak dapat mengakses metode kelas dasar itu. Artikel DevX.com yang Anda tunjukkan salah (warisan publik). Coba kode dalam jawaban saya. Meskipun metode kelas dasar virtual pribadi, kelas turunan dapat menimpanya. Mari kita tidak membingungkan kemampuan untuk menimpa metode kelas dasar virtual pribadi dengan kemampuan untuk memintanya.
Batal
@ Gentleman: @wilhelmtell menunjukkan kesalahan dalam jawaban / komentar saya. Klaim saya tentang pewarisan yang memengaruhi aksesibilitas kelas turunan dari metode kelas dasar tidak aktif. Saya telah menghapus komentar yang menyinggung jawaban Anda.
Batal
@ Batal, saya melihat bahwa kelas turunan dapat mengesampingkan metode virtual privat kelas dasarnya tetapi tidak dapat menggunakannya. Jadi, ini pada dasarnya adalah pola Metode Templat.
Buhake Sindi
0

TL; DR jawab:

Anda dapat memperlakukannya seperti tingkat enkapsulasi lainnya - di suatu tempat antara dilindungi dan pribadi : Anda tidak dapat memanggilnya dari kelas anak-anak, tetapi Anda dapat menimpanya.

Berguna saat menerapkan pola desain Metode Templat . Anda dapat menggunakan protected , tetapi privat bersama dengan virtual dapat dianggap sebagai pilihan yang lebih baik, karena enkapsulasi yang lebih baik.

jaskmar
sumber