Jangan mewarisi dari std :: vector

189

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::vectorantarmuka yang lain. Nah, ide pertama saya, sebagai warga negara yang taat hukum, adalah memiliki std::vectoranggota di MyVectorkelas. 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::memberdi 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 :)

Armen Tsirunyan
sumber
9
Jadi, pada dasarnya Anda bertanya apakah boleh melanggar aturan umum berdasarkan fakta bahwa Anda terlalu malas untuk mengimplementasikan kembali antarmuka penampung? Maka tidak, tidak. Lihat, Anda dapat memiliki yang terbaik dari kedua dunia jika Anda menelan pil pahit itu dan melakukannya dengan benar. Jangan menjadi pria itu. Tulis kode yang kuat.
Jim Brissom
7
Mengapa Anda tidak bisa / tidak ingin menambahkan fungsionalitas yang Anda butuhkan dengan fungsi non-anggota? Bagi saya, itu akan menjadi hal paling aman untuk dilakukan dalam skenario ini.
Simone
11
@ Jim: std::vectorAntarmuka 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.
sbi
9
Daging sebenarnya dari pertanyaan adalah dalam satu kalimat: "Saya ingin mereka menjadi anggota langsung dari vektor". Tidak ada hal lain dalam pertanyaan yang benar-benar penting. Mengapa Anda "menginginkan" ini? Apa masalah dengan hanya menyediakan fungsi ini sebagai bukan anggota?
Jalf
8
@JoshC: "Engkau" selalu lebih umum daripada "kamu akan", dan itu juga versi yang ditemukan dalam King James Bible (yang secara umum adalah apa yang orang singgung ketika mereka menulis "kamu tidak akan [...] "). Apa yang akan membuat Anda menyebutnya "salah mengeja"?
ruakh

Jawaban:

155

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 antara std::vectordan MyVector? Mana yang lebih baik untuk digunakan di sana-sini? Bagaimana jika saya harus pindah std::vectorke MyVector? Bolehkah saya menggunakan swap()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.

Stas
sumber
7
Satu-satunya pertentangan saya untuk ini adalah bahwa seseorang harus benar-benar tahu apa yang dia lakukan untuk melakukan ini. Misalnya, jangan masukkan anggota data tambahan ke dalam MyVectordan kemudian coba kirimkan ke fungsi yang menerima std::vector&atau std::vector*. Jika ada jenis tugas penyalinan yang terlibat menggunakan std :: vector * atau std :: vector &, kami memiliki masalah penguraian di mana anggota data baru MyVectortidak 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.
stinky472
13
std::vectorDestruction bukan virtual, oleh karena itu Anda tidak boleh mewarisi darinya
André Fratelli
2
Saya membuat kelas yang mewarisi std :: vektor untuk alasan ini: Saya punya kode lama dengan kelas vektor non-STL, dan saya ingin pindah ke STL. Saya menerapkan kembali kelas lama sebagai kelas turunan dari std :: vector, memungkinkan saya untuk terus menggunakan nama fungsi yang lama (misalnya, Hitung () daripada ukuran ()) dalam kode lama, sambil menulis kode baru menggunakan std :: vector fungsi. Saya tidak menambahkan anggota data, oleh karena itu std :: vector's destructor bekerja dengan baik untuk objek yang dibuat di heap.
Graham Asher
3
@GrahamAsher Jika Anda menghapus objek melalui pointer ke base, dan destructor bukan virtual, program Anda menunjukkan perilaku yang tidak terdefinisi. Salah satu hasil yang mungkin dari perilaku tidak terdefinisi adalah "itu bekerja dengan baik dalam tes saya". Lain adalah bahwa itu email nenek Anda riwayat browsing web Anda. Keduanya sesuai dengan standar C ++. Ini berubah dari satu ke yang lain dengan rilis titik kompiler, OS, atau fase bulan juga sesuai.
Yakk - Adam Nevraumont
2
@ GrahamAsher Tidak, setiap kali Anda menghapus objek apa pun melalui pointer ke base tanpa destructor virtual, itu adalah perilaku yang tidak terdefinisi di bawah standar. Saya mengerti apa yang Anda pikirkan sedang terjadi; Anda hanya salah. "destruktor kelas dasar disebut, dan berfungsi" adalah salah satu gejala yang mungkin (dan paling umum) dari perilaku tidak terdefinisi ini, karena itu adalah kode mesin naif yang biasanya dihasilkan oleh kompiler. Ini tidak membuatnya aman atau ide bagus untuk dilakukan.
Yakk - Adam Nevraumont
92

Seluruh 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 .

Kos
sumber
63

Alasan utama untuk tidak mewarisi dari std::vectorpublik adalah tidak adanya penghancur virtual yang secara efektif mencegah Anda dari penggunaan keturunan secara polimorfik. Secara khusus, Anda tidak diperbolehkan untuk deletesuatustd::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.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

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.

Basilevs
sumber
IIUC, tidak adanya destruktor virtual hanya masalah jika kelas turunan mengalokasikan sumber daya yang harus dibebaskan setelah kehancuran. (Mereka tidak akan dibebaskan dalam kasus penggunaan polimorfik karena konteks tanpa sadar mengambil kepemilikan objek yang diturunkan melalui pointer ke basis hanya akan memanggil destruktor basis ketika saatnya.) Masalah serupa muncul dari fungsi anggota lain yang ditimpa, jadi harus hati-hati diambil bahwa yang dasar sah untuk dipanggil. Tetapi tidak ada sumber daya tambahan, apakah ada alasan lain?
Peter - Reinstate Monica
2
vectorPenyimpanan yang dialokasikan bukan masalah - lagipula, vectordestruktor akan dipanggil baik - baik melalui pointer ke vector. Hanya saja bahwa larang standar deleteing toko bebas benda melalui ekspresi kelas dasar. Alasannya pasti bahwa mekanisme alokasi (de) dapat mencoba untuk menyimpulkan ukuran potongan memori untuk membebaskan dari deleteoperan, 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.
Peter - Reinstate Monica
@ Davidviser Saya pikir kami setuju di sana :-).
Peter - Pasang kembali Monica
@ Davidviser Ah, saya mengerti, Anda merujuk pada komentar pertama saya - ada IIUC dalam komentar itu, dan itu berakhir dengan pertanyaan; Saya melihat kemudian bahwa memang selalu terlarang. (Basilevs telah membuat pernyataan umum, "secara efektif mencegah", dan saya bertanya-tanya tentang cara spesifik yang mencegahnya.) Jadi ya, kami setuju: UB.
Peter - Reinstate Monica
@ Basilevs Itu pasti tidak sengaja. Tetap.
ThomasMcLeod
36

Jika Anda mempertimbangkan hal ini, Anda jelas telah membunuh pedant bahasa di kantor Anda. Dengan mereka keluar dari jalan, mengapa tidak lakukan saja

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

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.

Crashworks
sumber
Dan mengekspos wadah dan algoritma? Lihat jawaban Kos di atas.
bruno nery
19

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.

Karl Knechtel
sumber
5
Saya tidak yakin. Bisakah Anda memperbarui dengan beberapa kode yang diusulkan untuk menjelaskan alasannya?
Karl Knechtel
6
@Armen: selain estetika, apakah ada alasan bagus ?
snemarch
12
@Armen: Estetika yang lebih baik, dan kemurahan hati yang lebih besar, akan memberikan fungsi frontdan gratis backjuga. :) (Pertimbangkan juga contoh gratis begindan enddalam C ++ 0x dan boost.)
UncleBens
3
Saya masih tidak tahu apa yang salah dengan fungsi gratis. Jika Anda tidak menyukai "estetika" dari STL, mungkin C ++ adalah tempat yang salah untuk Anda, secara estetika. Dan menambahkan beberapa fungsi anggota tidak akan memperbaikinya, karena banyak algoritme lainnya masih merupakan fungsi bebas.
Frank Osterfeld
17
Sulit untuk men-cache hasil dari operasi yang berat dalam algoritma eksternal. Misalkan Anda harus menghitung jumlah semua elemen dalam vektor atau untuk menyelesaikan persamaan polinomial dengan elemen vektor sebagai koefisien. Operasi-operasi itu berat dan kemalasan akan bermanfaat bagi mereka. Tapi Anda tidak bisa memperkenalkannya tanpa membungkus atau mewarisi dari wadah.
Basilevs
14

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.

NPE
sumber
9
Kalimat pertama Anda benar 100%. :)
Steve Fallows
5
Sayangnya, kalimat kedua tidak. Dia belum banyak memikirkannya. Sebagian besar pertanyaan tidak relevan. Satu-satunya bagian yang menunjukkan motivasinya adalah "Saya ingin mereka secara langsung menjadi anggota vektor". Saya ingin. Tidak ada alasan mengapa ini diinginkan. Yang terdengar seperti dia tidak memikirkannya sama sekali .
Jalf
7

Tidak ada alasan untuk mewarisi dari std::vectorkecuali seseorang ingin membuat kelas yang bekerja berbeda dari std::vector, karena ia menangani dengan caranya sendiri rincian tersembunyi dari std::vectordefinisi, atau kecuali seseorang memiliki alasan ideologis untuk menggunakan objek dari kelas tersebut sebagai pengganti std::vectorYang itu. Namun, pencipta standar pada C ++ tidak menyediakan std::vectorantarmuka 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::vectorbukan polimorfik, dan jika tidak, tidak ada perbedaan apakah Anda mengekspos std::vectorantarmuka 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 bahwa std::vectors adalah semacam "ide sederhana", sehingga setiap kompleksitas dalam bentuk objek dari berbagai kelas yang mungkin di tempat mereka secara ideologis tidak digunakan.

Evgeniy
sumber
Jawaban yang bagus Selamat datang di SO!
Armen Tsirunyan
4

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:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...
huelner
sumber
3
"Anda hanya perlu destruktor virtual jika ukuran kelas dasar dan kelas turunan berbeda nad / atau Anda memiliki fungsi virtual (yang berarti v-table)." Klaim ini praktis benar, tetapi tidak secara teoritis
Armen Tsirunyan
2
ya, pada prinsipnya itu masih perilaku yang tidak terdefinisi.
Jalf
Jika Anda mengklaim bahwa ini adalah perilaku yang tidak terdefinisi, saya ingin melihat bukti (kutipan dari standar).
hmuelner
8
@hmuelner: Sayangnya, Armen dan jalf benar dalam hal ini. Dari [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>
Ben Voigt
1
Yang lucu, karena saya benar-benar berpikir perilaku itu tergantung pada keberadaan destruktor non-sepele (khususnya, bahwa kelas POD dapat dihancurkan melalui pointer-to-base).
Ben Voigt
3

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 deletepointer 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):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

Sebaliknya, ini akan selalu berhasil (dengan atau tanpa destruktor virtual):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

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 bentuk share_ptratau unique_ptr. Singkatnya, jangan lakukan deleteapa pun yang tidak Anda dapatkan langsung dari new.

pengguna877329
sumber
2

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.

jcoder
sumber
2

Saya juga mewarisi dari std::vectorbaru-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 untuk std::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!

Joel Andersson
sumber
10
Dalam pengalaman saya, kerusakan jangka panjang terburuk sering disebabkan oleh orang yang mencoba sesuatu yang keliru, dan " sejauh ini belum mengalami (baca perhatikan ) ada masalah dengan itu".
Kecewa
0

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:

  1. Biarkan saya menambahkan cara pembungkus lain std::vectortanpa menulis banyak fungsi pembungkus.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Mewarisi dari std :: span bukannya std::vectordan menghindari masalah Dtor.
JiaHao Xu
sumber
0

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

  std::vector<T> names; names.reserve(1000);

kita bisa bilang

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

dan kemudian memiliki, bahkan sebagai anggota kelas,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(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_inini lebih baik dilayani oleh templat fungsi gratis:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

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:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
Nathan Myers
sumber