Metode virtual pribadi di C ++

125

Apa keuntungan membuat metode privat virtual di C ++?

Saya telah memperhatikan ini dalam proyek C ++ open source:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
silverburgh
sumber
9
Saya pikir pertanyaannya terbalik. Alasan untuk membuat sesuatu menjadi virtual selalu sama: untuk mengizinkan kelas turunan menimpanya. Jadi pertanyaannya adalah: apa keuntungan menjadikan metode virtual privat? Jawabannya adalah: jadikan semuanya pribadi secara default. :-)
ShreevatsaR
1
@ShreevatsaR Tapi Anda bahkan tidak menjawab pertanyaan Anda sendiri ......
Spencer
@ShreevatsaR Saya pikir maksud Anda mundur dengan cara yang berbeda: Apa keuntungan membuat metode virtual tidak privat?
Peter - Pulihkan Monica

Jawaban:

116

Herb Sutter telah menjelaskannya dengan sangat baik di sini .

Panduan # 2: Lebih suka menjadikan fungsi virtual bersifat pribadi.

Hal ini memungkinkan kelas turunan mengganti fungsi untuk menyesuaikan perilaku sesuai kebutuhan, tanpa mengekspos fungsi virtual lebih jauh secara langsung dengan membuatnya dapat dipanggil oleh kelas turunan (seperti yang mungkin dilakukan jika fungsi hanya dilindungi). Intinya adalah bahwa fungsi virtual ada untuk memungkinkan penyesuaian; kecuali mereka juga perlu dipanggil langsung dari dalam kode kelas turunan, tidak perlu menjadikannya apa pun kecuali pribadi

Prasoon Saurav
sumber
Seperti yang bisa Anda tebak dari jawaban saya, saya pikir pedoman Sutter # 3 lebih mendorong pedoman # 2 keluar jendela.
Spencer
66

Jika metodenya virtual, ia dapat diganti dengan kelas turunan, meskipun itu privat. Saat metode virtual dipanggil, versi yang diganti akan dipanggil.

(Bertentangan dengan Herb Sutter yang dikutip oleh Prasoon Saurav dalam jawabannya, C ++ FAQ Lite merekomendasikan agar virtual pribadi tidak , terutama karena sering membingungkan orang.)

sth
sumber
41
Tampaknya C ++ FAQ Lite telah mengubah rekomendasinya: " FAQ C ++ sebelumnya merekomendasikan menggunakan virtual yang dilindungi daripada virtual pribadi. Namun pendekatan virtual pribadi sekarang cukup umum sehingga kebingungan pemula tidak terlalu menjadi perhatian. "
Zack The Manusia
19
Namun, kebingungan para ahli tetap menjadi perhatian. Tak satu pun dari empat profesional C ++ yang duduk di sebelah saya mengetahui virtual pribadi.
Newtonx
12

Terlepas dari semua seruan untuk mendeklarasikan anggota virtual sebagai pribadi, argumen tersebut tidak masuk akal. Seringkali, kelas turunan yang menggantikan fungsi virtual harus memanggil versi kelas dasar. Tidak bisa jika dideklarasikan private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Anda harus mendeklarasikan metode kelas dasar protected.

Kemudian, Anda harus mengambil langkah buruk untuk menunjukkan melalui komentar bahwa metode tersebut harus diganti tetapi tidak dipanggil.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Jadi, pedoman Herb Sutter # 3 ... Tapi kudanya sudah keluar dari kandang.

Saat Anda mendeklarasikan sesuatu, protectedAnda secara implisit mempercayai penulis kelas turunan mana pun untuk memahami dan menggunakan internal terlindungi dengan benar, seperti halnya frienddeklarasi menyiratkan kepercayaan yang lebih dalam bagi privateanggota.

Pengguna yang mendapatkan perilaku buruk karena melanggar kepercayaan itu (misalnya diberi label 'tidak mengerti' dengan tidak repot-repot membaca dokumentasi Anda) hanya menyalahkan diri mereka sendiri.

Pembaruan : Saya memiliki beberapa umpan balik yang mengklaim Anda dapat "merangkai" implementasi fungsi virtual dengan cara ini menggunakan fungsi virtual pribadi. Jika demikian, saya pasti ingin melihatnya.

Kompiler C ++ yang saya gunakan pasti tidak akan membiarkan implementasi kelas turunan memanggil implementasi kelas dasar pribadi.

Jika komite C ++ melonggarkan "pribadi" untuk mengizinkan akses khusus ini, saya akan memilih semua fungsi virtual pribadi. Saat ini, kami masih disarankan untuk mengunci pintu gudang setelah kudanya dicuri.

Spencer
sumber
3
Saya menemukan argumen Anda tidak valid. Anda sebagai pengembang API harus berjuang untuk antarmuka yang sulit digunakan secara tidak benar dan tidak mengatur pengembang lain untuk kesalahan Anda sendiri dalam melakukannya. Apa yang ingin Anda lakukan dalam contoh Anda dapat diimplementasikan hanya dengan metode virtual pribadi.
sigy
1
Bukan itu yang saya katakan. Tetapi Anda dapat merestrukturisasi kode Anda untuk mencapai efek yang sama tanpa perlu memanggil fungsi kelas dasar pribadi
sigy
3
Dalam contoh Anda, Anda ingin memperluas perilaku set_data. Instruksi m_data = ndata;dan cleanup();dengan demikian dapat dianggap sebagai invarian yang harus dimiliki untuk semua implementasi. Oleh karena itu jadikan cleanup()non-virtual dan pribadi. Tambahkan panggilan ke metode privat lain yang virtual dan titik ekstensi kelas Anda. Sekarang tidak perlu lagi kelas turunan Anda memanggil basis cleanup(), kode Anda tetap bersih dan antarmuka Anda sulit digunakan secara tidak benar.
sigy
2
@sigy Itu hanya memindahkan tiang gawang. Anda perlu melihat melampaui contoh miminal. Jika ada turunan lebih lanjut yang perlu memanggil semua cleanup()s dalam rantai, argumen akan berantakan. Atau apakah Anda merekomendasikan fungsi virtual tambahan untuk setiap turunan dalam rantai? Ick. Bahkan Herb Sutter mengizinkan fungsi virtual yang dilindungi sebagai celah dalam pedomannya # 3. Bagaimanapun, tanpa beberapa kode yang sebenarnya Anda tidak akan pernah meyakinkan saya.
Spencer
2
Maka mari kita setuju untuk tidak setuju;)
sigy
9

Saya pertama kali menemukan konsep ini saat membaca 'Efektif C ++' Scott Meyers, Item 35: Pertimbangkan alternatif untuk fungsi virtual. Saya ingin merujuk Scott Mayers untuk orang lain yang mungkin tertarik.

Ini adalah bagian dari Pola Metode Template melalui idiom Antarmuka Non-Virtual : metode yang dihadapi publik bukanlah virtual; sebaliknya, mereka membungkus panggilan metode virtual yang bersifat pribadi. Kelas dasar kemudian dapat menjalankan logika sebelum dan sesudah panggilan fungsi virtual pribadi:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Saya pikir ini adalah pola desain yang sangat menarik dan saya yakin Anda dapat melihat bagaimana kontrol tambahan berguna.

  • Mengapa membuat fungsi virtual private? Alasan terbaiknya adalah kami telah menyediakan publicmetode menghadapi.
  • Mengapa tidak dibuat saja protectedagar saya dapat menggunakan metode tersebut untuk hal-hal menarik lainnya? Saya kira itu akan selalu tergantung pada desain Anda dan bagaimana Anda percaya kelas dasar cocok. Saya berpendapat bahwa pembuat kelas turunan harus fokus pada penerapan logika yang diperlukan; yang lainnya sudah diurus. Juga, ada masalah enkapsulasi.

Dari perspektif C ++, benar-benar sah untuk menimpa metode virtual pribadi meskipun Anda tidak dapat memanggilnya dari kelas Anda. Ini mendukung desain yang dijelaskan di atas.

Pooven
sumber
3

Saya menggunakannya untuk memungkinkan kelas turunan untuk "mengisi kekosongan" untuk kelas dasar tanpa mengekspos lubang seperti itu kepada pengguna akhir. Misalnya, saya memiliki objek yang sangat stateful yang berasal dari basis umum, yang hanya dapat mengimplementasikan 2/3 dari mesin status keseluruhan (kelas turunan menyediakan 1/3 sisanya tergantung pada argumen templat, dan basis tidak dapat menjadi templat untuk alasan lain).

Saya PERLU memiliki kelas dasar umum untuk membuat banyak API publik bekerja dengan benar (saya menggunakan templat variadic), tetapi saya tidak bisa membiarkan objek itu keluar ke alam liar. Lebih buruk lagi, jika saya meninggalkan kawah di mesin negara - dalam bentuk fungsi virtual murni - di mana saja kecuali dalam "Pribadi", saya mengizinkan pengguna yang pandai atau tidak mengerti yang berasal dari salah satu kelas anaknya untuk mengganti metode yang tidak boleh disentuh pengguna. Jadi, saya menempatkan 'otak' mesin negara dalam fungsi virtual PRIVATE. Kemudian turunan langsung dari kelas dasar mengisi kekosongan pada penggantian NON-virtual mereka, dan pengguna dapat dengan aman menggunakan objek yang dihasilkan atau membuat kelas turunan lebih lanjut mereka sendiri tanpa khawatir mengacaukan mesin status.

Adapun argumen bahwa Anda seharusnya tidak MEMILIKI metode virtual publik, saya katakan BS. Pengguna dapat menimpa virtual privat dengan tidak semestinya semudah yang publik- mereka mendefinisikan kelas baru. Jika publik tidak boleh mengubah API tertentu, jangan menjadikannya virtual sama sekali dalam objek yang dapat diakses publik.

Zack Yezek
sumber