Ok, ini benar-benar sulit untuk diakui, tetapi saya memiliki godaan yang kuat saat ini untuk diwarisi std::vector
.
Saya membutuhkan sekitar 10 algoritma yang disesuaikan untuk vektor dan saya ingin mereka menjadi anggota vektor secara langsung. Tapi tentu saja saya juga ingin memiliki std::vector
antarmuka yang lain. Nah, ide pertama saya, sebagai warga negara yang taat hukum, adalah memiliki std::vector
anggota di MyVector
kelas. Tapi kemudian saya harus secara manual memberikan kembali semua antarmuka std :: vector. Terlalu banyak mengetik. Selanjutnya, saya berpikir tentang warisan pribadi, sehingga alih-alih memberikan kembali metode saya akan menulis banyak using std::vector::member
di bagian publik. Ini membosankan sebenarnya juga.
Dan di sinilah saya, saya benar-benar berpikir bahwa saya dapat mewarisi dari publik std::vector
, tetapi memberikan peringatan dalam dokumentasi bahwa kelas ini tidak boleh digunakan secara polimorfik. Saya pikir sebagian besar pengembang cukup kompeten untuk memahami bahwa ini seharusnya tidak digunakan secara polimorfik.
Apakah keputusan saya benar-benar tidak dapat dibenarkan? Jika demikian, mengapa? Bisakah Anda memberikan alternatif yang akan membuat anggota tambahan benar-benar anggota tetapi tidak akan melibatkan mengetik ulang semua antarmuka vektor? Saya meragukannya, tetapi jika Anda bisa, saya hanya akan bahagia.
Juga, terlepas dari kenyataan bahwa beberapa orang idiot dapat menulis sesuatu seperti
std::vector<int>* p = new MyVector
apakah ada bahaya realistis lain dalam menggunakan MyVector? Dengan mengatakan realistis saya membuang hal-hal seperti bayangkan fungsi yang mengambil pointer ke vektor ...
Yah, saya sudah menyatakan kasus saya. Saya telah berdosa. Sekarang terserah Anda untuk memaafkan saya atau tidak :)
std::vector
Antarmuka cukup besar, dan ketika C ++ 1x datang, itu akan sangat berkembang. Itu banyak untuk diketik dan lebih berkembang dalam beberapa tahun. Saya pikir ini adalah alasan yang baik untuk mempertimbangkan warisan alih-alih penahanan - jika seseorang mengikuti premis bahwa fungsi-fungsi tersebut harus menjadi anggota (yang saya ragu). Aturan untuk tidak berasal dari wadah STL adalah bahwa mereka tidak polimorfik. Jika Anda tidak menggunakannya, itu tidak berlaku.Jawaban:
Sebenarnya, tidak ada yang salah dengan warisan publik
std::vector
. Jika Anda membutuhkan ini, lakukan saja.Saya sarankan melakukan itu hanya jika itu benar - benar diperlukan. Hanya jika Anda tidak dapat melakukan apa yang Anda inginkan dengan fungsi bebas (mis. Harus mempertahankan beberapa keadaan).
Masalahnya adalah
MyVector
adalah entitas baru. Ini berarti pengembang C ++ baru harus tahu apa itu sebelum menggunakannya. Apa perbedaan antarastd::vector
danMyVector
? Mana yang lebih baik untuk digunakan di sana-sini? Bagaimana jika saya harus pindahstd::vector
keMyVector
? Bolehkah saya menggunakanswap()
atau tidak?Jangan menghasilkan entitas baru hanya untuk membuat sesuatu terlihat lebih baik. Entitas-entitas ini (terutama, umum seperti itu) tidak akan hidup dalam kekosongan. Mereka akan hidup dalam lingkungan campuran dengan entropi yang terus meningkat.
sumber
MyVector
dan kemudian coba kirimkan ke fungsi yang menerimastd::vector&
ataustd::vector*
. Jika ada jenis tugas penyalinan yang terlibat menggunakan std :: vector * atau std :: vector &, kami memiliki masalah penguraian di mana anggota data baruMyVector
tidak akan disalin. Hal yang sama berlaku untuk memanggil swap melalui pointer / referensi dasar. Saya cenderung berpikir segala jenis hierarki warisan yang berisiko mengiris objek adalah yang buruk.std::vector
Destruction bukanvirtual
, oleh karena itu Anda tidak boleh mewarisi darinyaSeluruh STL dirancang sedemikian rupa sehingga algoritma dan wadah terpisah .
Ini mengarah pada konsep berbagai jenis iterator: const iterators, iterators akses acak, dll.
Karena itu saya sarankan Anda untuk menerima konvensi ini dan merancang algoritma Anda sedemikian rupa sehingga mereka tidak akan peduli tentang wadah apa yang sedang mereka kerjakan - dan mereka hanya akan memerlukan jenis iterator tertentu yang mereka perlukan untuk menjalankannya. operasi.
Juga, izinkan saya mengarahkan Anda ke beberapa komentar baik oleh Jeff Attwood .
sumber
Alasan utama untuk tidak mewarisi dari
std::vector
publik adalah tidak adanya penghancur virtual yang secara efektif mencegah Anda dari penggunaan keturunan secara polimorfik. Secara khusus, Anda tidak diperbolehkan untukdelete
suatustd::vector<T>*
yang benar-benar poin pada objek yang diturunkan (bahkan jika kelas turunan menambahkan tidak ada anggota), namun compiler umumnya tidak dapat memperingatkan Anda tentang hal itu.Warisan pribadi diizinkan dalam kondisi ini. Karena itu saya sarankan menggunakan warisan pribadi dan meneruskan metode yang diperlukan dari orang tua seperti yang ditunjukkan di bawah ini.
Pertama-tama Anda harus mempertimbangkan refactoring algoritma Anda untuk abstrak jenis wadah mereka beroperasi dan biarkan mereka sebagai fungsi templated gratis, seperti yang ditunjukkan oleh sebagian besar penjawab. Ini biasanya dilakukan dengan membuat suatu algoritma menerima sepasang iterator alih-alih wadah sebagai argumen.
sumber
vector
Penyimpanan yang dialokasikan bukan masalah - lagipula,vector
destruktor akan dipanggil baik - baik melalui pointer kevector
. Hanya saja bahwa larang standardelete
ing toko bebas benda melalui ekspresi kelas dasar. Alasannya pasti bahwa mekanisme alokasi (de) dapat mencoba untuk menyimpulkan ukuran potongan memori untuk membebaskan daridelete
operan, misalnya ketika ada beberapa arena alokasi untuk objek dengan ukuran tertentu. Pembatasan ini tidak, afaics, tidak berlaku untuk penghancuran objek normal dengan durasi penyimpanan statis atau otomatis.Jika Anda mempertimbangkan hal ini, Anda jelas telah membunuh pedant bahasa di kantor Anda. Dengan mereka keluar dari jalan, mengapa tidak lakukan saja
Itu akan menghindari semua kesalahan yang mungkin terjadi yang secara tidak sengaja menyebarkan kelas MyVector Anda, dan Anda masih dapat mengakses semua ops vektor hanya dengan menambahkan sedikit
.v
.sumber
Apa yang ingin kamu capai? Hanya menyediakan beberapa fungsionalitas?
Cara C ++ idiomatis untuk melakukan ini adalah dengan hanya menulis beberapa fungsi gratis yang mengimplementasikan fungsi tersebut. Kemungkinannya adalah Anda tidak benar-benar memerlukan std :: vector, khusus untuk fungsionalitas yang Anda laksanakan, yang berarti Anda benar-benar kehilangan reusability dengan mencoba mewarisi dari std :: vector.
Saya akan sangat menyarankan Anda untuk melihat perpustakaan dan header standar, dan merenungkan bagaimana mereka bekerja.
sumber
front
dan gratisback
juga. :) (Pertimbangkan juga contoh gratisbegin
danend
dalam C ++ 0x dan boost.)Saya pikir sangat sedikit aturan yang harus diikuti secara membabi buta 100% dari waktu. Sepertinya Anda sudah banyak berpikir, dan yakin bahwa inilah jalan yang harus ditempuh. Jadi - kecuali seseorang datang dengan alasan spesifik yang bagus untuk tidak melakukan ini - saya pikir Anda harus melanjutkan rencana Anda.
sumber
Tidak ada alasan untuk mewarisi dari
std::vector
kecuali seseorang ingin membuat kelas yang bekerja berbeda daristd::vector
, karena ia menangani dengan caranya sendiri rincian tersembunyi daristd::vector
definisi, atau kecuali seseorang memiliki alasan ideologis untuk menggunakan objek dari kelas tersebut sebagai penggantistd::vector
Yang itu. Namun, pencipta standar pada C ++ tidak menyediakanstd::vector
antarmuka apa pun (dalam bentuk anggota yang dilindungi) yang dapat diwariskan oleh kelas yang diwariskan untuk meningkatkan vektor dengan cara tertentu. Memang, mereka tidak memiliki cara untuk memikirkan aspek tertentu yang mungkin perlu ekstensi atau menyempurnakan implementasi tambahan, sehingga mereka tidak perlu berpikir untuk menyediakan antarmuka semacam itu untuk tujuan apa pun.Alasan untuk opsi kedua hanya bisa ideologis, karena
std::vector
bukan polimorfik, dan jika tidak, tidak ada perbedaan apakah Anda mengeksposstd::vector
antarmuka publik melalui warisan publik atau melalui keanggotaan publik. (Misalkan Anda perlu menyimpan beberapa status di objek Anda sehingga Anda tidak bisa lolos dengan fungsi bebas). Pada nada yang kurang terdengar dan dari sudut pandang ideologis, tampak bahwastd::vector
s adalah semacam "ide sederhana", sehingga setiap kompleksitas dalam bentuk objek dari berbagai kelas yang mungkin di tempat mereka secara ideologis tidak digunakan.sumber
Dalam istilah praktis: Jika Anda tidak memiliki anggota data di kelas turunan Anda, Anda tidak memiliki masalah, bahkan dalam penggunaan polimorfik. Anda hanya perlu destruktor virtual jika ukuran kelas dasar dan kelas turunan berbeda dan / atau Anda memiliki fungsi virtual (yang berarti tabel-v).
TETAPI dalam teori: Dari [expr.delete] dalam C ++ 0x FCD: Pada alternatif pertama (hapus objek), jika tipe statis objek yang akan dihapus berbeda dari tipe dinamisnya, tipe statis akan menjadi kelas dasar dari tipe dinamis dari objek yang akan dihapus dan tipe statis harus memiliki destruktor virtual atau perilaku tidak terdefinisi.
Tetapi Anda dapat memperoleh secara pribadi dari std :: vector tanpa masalah. Saya telah menggunakan pola berikut:
sumber
[expr.delete]
dalam C ++ 0x FCD: <quote> Di alternatif pertama (hapus objek), jika tipe statis objek yang akan dihapus berbeda dari tipe dinamisnya, tipe statis akan menjadi kelas dasar dari tipe dinamis dari objek yang akan dihapus dan tipe statis akan memiliki destruktor virtual atau perilaku tidak terdefinisi. </quote>Jika Anda mengikuti gaya C ++ yang baik, tidak adanya fungsi virtual bukanlah masalahnya, tetapi mengiris (lihat https://stackoverflow.com/a/14461532/877329 )
Mengapa tidak adanya fungsi virtual bukan masalah? Karena suatu fungsi tidak boleh mencoba ke
delete
pointer apa pun yang diterimanya, karena ia tidak memiliki kepemilikan terhadapnya. Oleh karena itu, jika mengikuti kebijakan kepemilikan yang ketat, penghancur virtual tidak diperlukan. Misalnya, ini selalu salah (dengan atau tanpa destruktor virtual):Sebaliknya, ini akan selalu berhasil (dengan atau tanpa destruktor virtual):
Jika objek dibuat oleh pabrik, pabrik juga harus mengembalikan pointer ke deleter yang berfungsi, yang seharusnya digunakan sebagai ganti
delete
, karena pabrik dapat menggunakan tumpukannya sendiri. Penelepon bisa mendapatkannya dalam bentukshare_ptr
atauunique_ptr
. Singkatnya, jangan lakukandelete
apa pun yang tidak Anda dapatkan langsung darinew
.sumber
Ya itu aman selama Anda berhati-hati untuk tidak melakukan hal-hal yang tidak aman ... Saya tidak berpikir saya pernah melihat orang menggunakan vektor dengan yang baru sehingga dalam praktiknya Anda mungkin akan baik-baik saja. Namun, itu bukan idiom umum dalam c ++ ....
Apakah Anda dapat memberikan informasi lebih lanjut tentang apa algoritma itu?
Kadang-kadang Anda akhirnya turun satu jalan dengan desain dan kemudian tidak dapat melihat jalur lain yang mungkin Anda ambil - fakta bahwa Anda mengklaim perlu vektor dengan 10 algoritma baru membunyikan lonceng alarm untuk saya - apakah benar-benar ada 10 tujuan umum algoritma yang dapat diimplementasikan oleh vektor, atau Anda mencoba membuat objek yang merupakan vektor tujuan umum DAN yang berisi fungsi spesifik aplikasi?
Saya tentu tidak mengatakan bahwa Anda tidak boleh melakukan ini, hanya saja dengan informasi yang Anda berikan bel alarm berdering yang membuat saya berpikir bahwa mungkin ada sesuatu yang salah dengan abstraksi Anda dan ada cara yang lebih baik untuk mencapai apa yang Anda ingin.
sumber
Saya juga mewarisi dari
std::vector
baru-baru ini, dan menemukan itu sangat berguna dan sejauh ini saya belum mengalami masalah dengannya.Kelas saya adalah kelas matriks jarang, yang berarti bahwa saya perlu menyimpan elemen matriks saya di suatu tempat, yaitu dalam
std::vector
. Alasan saya untuk mewarisi adalah bahwa saya agak terlalu malas untuk menulis antarmuka ke semua metode dan juga saya menghubungkan kelas ke Python melalui SWIG, di mana sudah ada kode antarmuka yang baik untukstd::vector
. Saya merasa lebih mudah untuk memperluas kode antarmuka ini ke kelas saya daripada menulis yang baru dari awal.Satu-satunya masalah yang saya bisa melihat dengan pendekatan ini tidak begitu banyak dengan destructor non-virtual, melainkan beberapa metode lain, yang saya ingin overload, seperti
push_back()
,resize()
,insert()
dll warisan Swasta bisa memang menjadi pilihan yang baik.Terima kasih!
sumber
Di sini, izinkan saya memperkenalkan 2 cara lagi untuk melakukan yang Anda inginkan. Satu adalah cara lain untuk membungkus
std::vector
, yang lain adalah cara untuk mewarisi tanpa memberi pengguna kesempatan untuk menghancurkan apa pun:std::vector
tanpa menulis banyak fungsi pembungkus.std::vector
dan menghindari masalah Dtor.sumber
Pertanyaan ini dijamin akan menghasilkan cengkeraman mutiara yang sesak napas, tetapi pada kenyataannya tidak ada alasan yang dapat dipertahankan untuk menghindari, atau "entitas yang berlipat ganda yang tidak perlu" untuk menghindari, derivasi dari wadah Standar. Ekspresi yang paling sederhana, sesingkat mungkin adalah yang paling jelas, dan terbaik.
Anda perlu melakukan semua perawatan yang biasa untuk semua jenis turunan, tetapi tidak ada yang istimewa dengan kasing standar. Mengesampingkan fungsi anggota basis bisa rumit, tetapi itu tidak bijaksana jika dilakukan dengan basis non-virtual, jadi tidak banyak yang istimewa di sini. Jika Anda menambahkan anggota data, Anda perlu khawatir tentang mengiris jika anggota harus tetap konsisten dengan konten basis, tetapi sekali lagi itu sama untuk basis apa pun.
Tempat di mana saya menemukan berasal dari wadah standar sangat berguna adalah untuk menambahkan konstruktor tunggal yang melakukan inisialisasi yang diperlukan, tanpa kemungkinan kebingungan atau pembajakan oleh konstruktor lain. (Saya melihat Anda, konstruktor initialization_list!) Kemudian, Anda dapat dengan bebas menggunakan objek yang dihasilkan, diiris - kirimkan dengan merujuk ke sesuatu yang mengharapkan basis, pindahkan darinya ke turunan basis, apa pun yang Anda miliki. Tidak ada kasus tepi yang perlu dikhawatirkan, kecuali itu akan mengganggu Anda untuk mengikat argumen templat ke kelas turunan.
Tempat di mana teknik ini akan segera berguna dalam C ++ 20 adalah reservasi. Di mana kita mungkin telah menulis
kita bisa bilang
dan kemudian memiliki, bahkan sebagai anggota kelas,
(sesuai preferensi) dan tidak perlu menulis konstruktor hanya untuk memanggil cadangan () pada mereka.
(Alasan itu
reserve_in
, secara teknis, perlu menunggu C ++ 20 adalah bahwa Standar sebelumnya tidak memerlukan kapasitas vektor kosong untuk dipertahankan di seluruh gerakan. Itu diakui sebagai pengawasan, dan secara wajar dapat diharapkan untuk diperbaiki sebagai cacat dalam waktu untuk '20. Kita juga dapat mengharapkan perbaikan, secara efektif, mundur ke Standar sebelumnya, karena semua implementasi yang ada benar-benar melestarikan kapasitas lintas gerakan, Standar hanya tidak membutuhkannya. pemesanan senjata hampir selalu hanya merupakan pengoptimalan saja.)Beberapa berpendapat bahwa kasus
reserve_in
ini lebih baik dilayani oleh templat fungsi gratis:Alternatif semacam itu tentu saja layak - dan bahkan, kadang-kadang, menjadi jauh lebih cepat, karena * RVO. Tetapi pilihan derivasi atau fungsi bebas harus dibuat berdasarkan kemampuannya sendiri, dan bukan dari takhayul tak berdasar (heh!) Tentang diturunkan dari komponen Standar. Dalam contoh penggunaan di atas, hanya bentuk kedua yang akan berfungsi dengan fungsi bebas; walaupun di luar konteks kelas dapat ditulis sedikit lebih ringkas:
sumber