Tidak seperti warisan yang dilindungi, warisan pribadi C ++ masuk ke dalam pengembangan C ++ arus utama. Namun, saya masih belum menemukan manfaat yang baik untuk itu.
Kapan kalian menggunakannya?
Catatan setelah penerimaan jawaban: Ini BUKAN jawaban lengkap. Bacalah jawaban lain seperti di sini (secara konseptual) dan di sini (baik teoretis maupun praktis) jika Anda tertarik dengan pertanyaannya. Ini hanyalah trik mewah yang dapat dicapai dengan warisan pribadi. Meskipun mewah , ini bukanlah jawaban atas pertanyaan tersebut.
Selain penggunaan dasar dari warisan pribadi yang ditunjukkan di C ++ FAQ (ditautkan di komentar orang lain), Anda dapat menggunakan kombinasi warisan pribadi dan virtual untuk menyegel kelas (dalam terminologi .NET) atau untuk membuat kelas final (dalam terminologi Java) . Ini bukan penggunaan umum, tapi bagaimanapun juga menurut saya ini menarik:
class ClassSealer {
private:
friend class Sealed;
ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{
// ...
};
class FailsToDerive : public Sealed
{
// Cannot be instantiated
};
Sealed dapat dipakai. Ini berasal dari ClassSealer dan dapat memanggil konstruktor pribadi secara langsung karena ini adalah teman.
FailsToDerive tidak akan dikompilasi karena harus memanggil konstruktor ClassSealer secara langsung (persyaratan warisan virtual), tetapi tidak bisa karena bersifat pribadi di kelas Sealed dan dalam hal ini FailsToDerive bukan teman ClassSealer .
EDIT
Disebutkan dalam komentar bahwa ini tidak dapat dibuat generik pada saat menggunakan CRTP. Standar C ++ 11 menghilangkan batasan itu dengan menyediakan sintaks yang berbeda untuk berteman dengan argumen template:
template <typename T>
class Seal {
friend T; // not: friend class T!!!
Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...
Tentu saja ini semua bisa diperdebatkan, karena C ++ 11 menyediakan final
kata kunci kontekstual untuk tujuan ini:
class Sealed final // ...
Saya menggunakannya sepanjang waktu. Beberapa contoh di luar kepala saya:
Contoh tipikal diturunkan secara pribadi dari wadah STL:
sumber
push_back
,MyVector
dapatkan secara gratis.template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }
atau menulis menggunakanBase::f;
. Jika Anda menginginkan sebagian besar fungsionalitas dan fleksibilitas yang diberikan oleh private inheritance dan sebuahusing
pernyataan, Anda memiliki monster itu untuk setiap fungsi (dan jangan lupa tentangconst
danvolatile
kelebihan beban!).Penggunaan kanonik warisan pribadi adalah hubungan "diimplementasikan dalam" (terima kasih kepada Scott Meyers 'C ++ Efektif' untuk kata-kata ini). Dengan kata lain, antarmuka eksternal dari kelas yang mewarisi tidak memiliki hubungan (terlihat) dengan kelas yang diwarisi, tetapi ia menggunakannya secara internal untuk mengimplementasikan fungsinya.
sumber
Salah satu kegunaan private inheritence adalah ketika Anda memiliki kelas yang mengimplementasikan antarmuka, yang kemudian didaftarkan dengan beberapa objek lain. Anda membuat antarmuka itu menjadi pribadi sehingga kelas itu sendiri harus mendaftar dan hanya objek tertentu yang didaftarkannya yang dapat menggunakan fungsi tersebut.
Sebagai contoh:
Oleh karena itu, kelas FooUser dapat memanggil metode pribadi FooImplementer melalui antarmuka FooInterface, sedangkan kelas eksternal lainnya tidak bisa. Ini adalah pola yang bagus untuk menangani callback tertentu yang didefinisikan sebagai antarmuka.
sumber
Saya pikir bagian penting dari C ++ FAQ Lite adalah:
Jika ragu, Anda sebaiknya memilih komposisi daripada warisan pribadi.
sumber
Saya merasa berguna untuk antarmuka (yaitu kelas abstrak) yang saya warisi di mana saya tidak ingin kode lain menyentuh antarmuka (hanya kelas yang mewarisi).
[diedit dalam contoh]
Ambil contoh yang ditautkan di atas. Mengatakan itu
adalah mengatakan bahwa Wilma meminta Fred untuk dapat menjalankan fungsi anggota tertentu, atau, lebih tepatnya mengatakan bahwa Wilma adalah sebuah antarmuka . Karenanya, seperti yang disebutkan dalam contoh
komentar tentang efek yang diinginkan dari pemrogram yang harus memenuhi persyaratan antarmuka kami, atau memecahkan kode. Dan, karena fredCallsWilma () dilindungi hanya teman dan kelas turunan yang dapat menyentuhnya, yaitu antarmuka yang diwariskan (kelas abstrak) yang hanya dapat disentuh oleh kelas yang mewarisi (dan teman).
[diedit di contoh lain]
Halaman ini secara singkat membahas antarmuka pribadi (dari sudut lain).
sumber
Kadang-kadang saya merasa berguna untuk menggunakan private inheritance ketika saya ingin mengekspos antarmuka yang lebih kecil (misalnya koleksi) di antarmuka lain, di mana implementasi collection memerlukan akses ke status kelas yang mengekspos, dengan cara yang mirip dengan kelas dalam di Jawa.
Kemudian jika SomeCollection perlu mengakses BigClass, itu bisa
static_cast<BigClass *>(this)
. Tidak perlu ada anggota data tambahan yang menghabiskan ruang.sumber
BigClass
apakah ada dalam contoh ini? Menurut saya ini menarik, tetapi wajah saya menjerit keras.Saya menemukan aplikasi bagus untuk warisan pribadi, meskipun penggunaannya terbatas.
Masalah untuk dipecahkan
Misalkan Anda diberi C API berikut:
Sekarang tugas Anda adalah mengimplementasikan API ini menggunakan C ++.
Pendekatan C-ish
Tentu saja kita bisa memilih gaya implementasi C-ish seperti ini:
Tetapi ada beberapa kelemahan:
struct
kesalahanstruct
Pendekatan C ++
Kami diizinkan menggunakan C ++, jadi mengapa tidak menggunakan kekuatan penuhnya?
Memperkenalkan manajemen sumber daya otomatis
Masalah di atas pada dasarnya semua terkait dengan manajemen sumber daya manual. Solusi yang terlintas dalam pikiran adalah mewarisi dari
Widget
dan menambahkan instance pengelolaan sumber daya ke kelas turunanWidgetImpl
untuk setiap variabel:Ini menyederhanakan implementasi sebagai berikut:
Seperti ini kami memperbaiki semua masalah di atas. Namun klien masih bisa melupakan penyetel
WidgetImpl
dan menetapkan keWidget
anggota secara langsung.Warisan pribadi memasuki panggung
Untuk merangkum
Widget
anggota kami menggunakan warisan pribadi. Sayangnya kita sekarang membutuhkan dua fungsi tambahan untuk digunakan di antara kedua kelas:Ini membuat adaptasi berikut diperlukan:
Solusi ini menyelesaikan semua masalah. Tidak ada manajemen memori manual dan
Widget
dikemas dengan baik sehinggaWidgetImpl
tidak memiliki anggota data publik lagi. Itu membuat implementasi mudah digunakan dengan benar dan sulit (tidak mungkin?) Untuk digunakan salah.Potongan kode membentuk contoh kompilasi di Coliru .
sumber
Jika kelas turunan - perlu menggunakan kembali kode dan - Anda tidak dapat mengubah kelas dasar dan - melindungi metodenya menggunakan anggota basis di bawah kunci.
maka Anda harus menggunakan warisan pribadi, jika tidak, Anda akan menghadapi bahaya metode dasar tidak terkunci yang diekspor melalui kelas turunan ini.
sumber
Terkadang ini bisa menjadi alternatif untuk agregasi , misalnya jika Anda menginginkan agregasi tetapi dengan perilaku entitas agregat yang diubah (menimpa fungsi virtual).
Tapi Anda benar, tidak banyak contoh dari dunia nyata.
sumber
Private Inheritance untuk digunakan jika relasi bukan "adalah", tetapi kelas baru dapat "diimplementasikan dalam istilah kelas yang sudah ada" atau kelas baru "berfungsi seperti" kelas yang sudah ada.
Contoh dari "standar pengkodean C ++ oleh Andrei Alexandrescu, Herb Sutter": - Pertimbangkan bahwa dua kelas Square dan Rectangle masing-masing memiliki fungsi virtual untuk mengatur tinggi dan lebarnya. Maka Square tidak dapat mewarisi dengan benar dari Rectangle, karena kode yang menggunakan Rectangle yang dapat dimodifikasi akan mengasumsikan bahwa SetWidth tidak mengubah ketinggian (apakah Rectangle secara eksplisit mendokumentasikan kontrak itu atau tidak), sedangkan Square :: SetWidth tidak dapat mempertahankan kontrak itu dan invarian kuadratnya sendiri di waktu yang sama. Tetapi Persegi juga tidak dapat mewarisi dengan benar dari Persegi, jika klien Persegi mengasumsikan misalnya bahwa luas persegi adalah lebar kuadratnya, atau jika mereka bergantung pada beberapa properti lain yang tidak berlaku untuk Persegi.
Persegi "adalah-a" persegi panjang (secara matematis) tetapi Persegi bukanlah Persegi (secara perilaku). Akibatnya, daripada "is-a", kami lebih memilih untuk mengatakan "works-like-a" (atau, jika Anda lebih suka, "usable-as-a") untuk membuat deskripsi tidak terlalu rentan terhadap kesalahpahaman.
sumber
Kelas memiliki invarian. Invarian ditetapkan oleh konstruktor. Namun, dalam banyak situasi, akan berguna untuk memiliki pandangan tentang status representasi objek (yang dapat Anda kirimkan melalui jaringan atau simpan ke file - DTO jika Anda mau). REST paling baik dilakukan dalam hal AggregateType. Ini terutama benar jika Anda benar konst. Mempertimbangkan:
Pada titik ini, Anda mungkin hanya menyimpan koleksi cache dalam container dan mencarinya saat konstruksi. Berguna jika ada pemrosesan nyata. Perhatikan bahwa cache adalah bagian dari QE: operasi yang didefinisikan pada QE mungkin berarti cache sebagian dapat digunakan kembali (misalnya, c tidak mempengaruhi jumlah); namun, jika tidak ada cache, ada baiknya untuk mencarinya.
Warisan pribadi hampir selalu dapat dimodelkan oleh anggota (menyimpan referensi ke pangkalan jika diperlukan). Tidak selalu layak untuk membuat model seperti itu; terkadang warisan adalah representasi yang paling efisien.
sumber
Jika Anda membutuhkan
std::ostream
dengan beberapa perubahan kecil (seperti dalam pertanyaan ini ) Anda mungkin perluMyStreambuf
yang berasal daristd::streambuf
dan terapkan perubahan di sanaMyOStream
yang diturunkan daristd::ostream
itu juga menginisialisasi dan mengelola sebuah instanceMyStreambuf
dan meneruskan pointer ke instance itu ke konstruktorstd::ostream
Ide pertama mungkin untuk menambahkan
MyStream
instance sebagai anggota data keMyOStream
kelas:Tapi kelas dasar dibangun sebelum anggota data apa pun sehingga Anda meneruskan pointer ke
std::streambuf
instance yang belum dibangunstd::ostream
yang yang merupakan perilaku tidak terdefinisi.Solusinya diusulkan dalam jawaban Ben atas pertanyaan yang disebutkan di atas , cukup mewarisi dari buffer aliran terlebih dahulu, kemudian dari aliran dan kemudian menginisialisasi aliran dengan
this
:Namun kelas yang dihasilkan juga bisa digunakan sebagai
std::streambuf
instance yang biasanya tidak diinginkan. Beralih ke warisan pribadi memecahkan masalah ini:sumber
Hanya karena C ++ memiliki fitur, bukan berarti itu berguna atau harus digunakan.
Saya akan mengatakan Anda tidak boleh menggunakannya sama sekali.
Jika Anda tetap menggunakannya, pada dasarnya Anda melanggar enkapsulasi, dan menurunkan kohesi. Anda meletakkan data di satu kelas, dan menambahkan metode yang memanipulasi data di kelas lain.
Seperti fitur C ++ lainnya, ini dapat digunakan untuk mencapai efek samping seperti menyegel kelas (seperti yang disebutkan dalam jawaban dribeas), tetapi ini tidak menjadikannya fitur yang bagus.
sumber