Mengapa konstruktor tidak diwariskan?

33

Saya bingung apa masalahnya jika konstruktor diwarisi dari kelas dasar. Cpp Primer Plus mengatakan,

Konstruktor berbeda dari metode kelas lain dalam hal mereka membuat objek baru, sedangkan metode lain dipanggil oleh objek yang ada . Ini adalah salah satu alasan konstruktor tidak diwariskan . Warisan berarti objek yang diturunkan dapat menggunakan metode kelas dasar, tetapi, dalam kasus konstruktor, objek tidak ada sampai setelah konstruktor menyelesaikan tugasnya.

Saya mengerti konstruktor dipanggil sebelum konstruksi objek selesai.

Bagaimana ini dapat menimbulkan masalah jika kelas anak mewarisi ( Dengan mewarisi maksud saya kelas anak dapat menimpa metode kelas induk dll. Tidak hanya mengakses metode kelas induk ) induk konstruktor?

Saya mengerti tidak ada keharusan secara eksplisit memanggil konstruktor dari dalam kode [belum saya sadari.] Kecuali saat membuat objek. Bahkan kemudian Anda dapat melakukannya dengan menggunakan beberapa mekanisme untuk memanggil konstuktor induk [Dalam cpp, menggunakan ::atau menggunakan member initialiser list, Dalam java menggunakan super]. Di Jawa ada penegakan untuk menyebutnya di baris 1, saya mengerti itu adalah untuk memastikan objek induk dibuat terlebih dahulu dan kemudian konstruksi objek anak dilanjutkan.

Itu bisa menimpanya . Tetapi, saya tidak dapat menemukan situasi di mana ini dapat menimbulkan masalah. Jika anak mewarisi konstruktor orang tua, apa yang salah?

Begitu juga ini hanya untuk menghindari mewarisi fungsi yang tidak perlu. Atau ada yang lebih dari itu?

Suvarna Pattayil
sumber
Tidak terlalu cepat dengan C ++ 11, tapi saya pikir ada beberapa bentuk pewarisan konstruktor di dalamnya.
yannis
3
Perlu diingat bahwa apa pun yang Anda lakukan di konstruktor, Anda harus berhati-hati dalam destruktor.
Manoj R
2
Saya pikir Anda perlu mendefinisikan apa yang dimaksud dengan "mewarisi konstruktor". Semua jawaban tampaknya memiliki variasi pada apa yang mereka pikirkan artinya "mewarisi konstruktor". Saya pikir tidak ada satupun yang cocok dengan interpretasi awal saya. Deskripsi "dalam kotak abu-abu" dari atas "Warisan berarti objek yang diturunkan dapat menggunakan metode kelas dasar" menyiratkan bahwa konstruktor diwarisi. Objek turunan pasti dan bisa menggunakan metode konstruktor kelas dasar, SELALU.
Dunk
1
Terima kasih atas klarifikasi mewarisi konstruktor, sekarang Anda bisa mendapatkan beberapa jawaban.
Dunk

Jawaban:

41

Tidak boleh ada warisan konstruktor yang tepat di C ++, karena konstruktor dari kelas turunan perlu melakukan tindakan tambahan yang tidak harus dilakukan dan tidak diketahui oleh konstruktor kelas dasar. Tindakan tambahan ini adalah inisialisasi anggota data dari kelas turunan (dan dalam implementasi yang khas, juga mengatur vpointer untuk merujuk ke kelas turunan vtable).

Ketika sebuah kelas dikonstruksi, ada sejumlah hal yang selalu perlu terjadi: Konstruktor dari kelas dasar (jika ada) dan anggota langsung perlu dipanggil dan jika ada fungsi virtual, vpointer harus diatur dengan benar. . Jika Anda tidak menyediakan konstruktor untuk kelas Anda, maka kompiler akan membuat yang melakukan tindakan yang diperlukan dan tidak ada yang lain. Jika Anda melakukan memberikan konstruktor, tapi kehilangan beberapa tindakan yang diperlukan (misalnya, inisialisasi beberapa anggota), maka compiler akan secara otomatis menambahkan tindakan yang hilang untuk konstruktor Anda. Dengan cara ini, kompilator memastikan bahwa setiap kelas memiliki setidaknya satu konstruktor dan bahwa setiap konstruktor sepenuhnya menginisialisasi objek yang dibuatnya.

Dalam C ++ 11, bentuk 'konstruktor pewarisan' telah diperkenalkan di mana Anda dapat menginstruksikan kompiler untuk menghasilkan seperangkat konstruktor untuk Anda yang menggunakan argumen yang sama dengan konstruktor dari kelas dasar dan yang hanya meneruskan argumen tersebut ke kelas dasar.
Meskipun secara resmi disebut warisan, itu tidak benar-benar begitu karena masih ada fungsi spesifik kelas turunan. Sekarang hanya dihasilkan oleh kompiler alih-alih ditulis secara eksplisit oleh Anda.

Fitur ini berfungsi seperti ini:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedsekarang memiliki dua konstruktor (tidak termasuk copy / pindah konstruktor). Satu yang mengambil int dan string dan yang hanya mengambil int.

Bart van Ingen Schenau
sumber
Jadi ini dengan asumsi, kelas anak tidak akan memiliki konstruktor sendiri? dan konstruktor yang diwarisi jelas tidak menyadari anggota anak dan inisialisasi yang harus dilakukan
Suvarna Pattayil
C ++ didefinisikan sedemikian rupa sehingga tidak mungkin bagi kelas untuk tidak memiliki konstruktor sendiri. Itulah mengapa saya tidak menganggap 'pewarisan konstruktor' sebagai warisan. Ini hanyalah cara untuk menghindari penulisan platplate yang membosankan.
Bart van Ingen Schenau
2
Saya pikir kebingungan berasal dari kenyataan bahwa bahkan konstruktor kosong bekerja di belakang layar. Jadi konstruktor benar-benar memiliki dua bagian: Pekerjaan internal dan bagian yang Anda tulis. Karena mereka tidak konstruktor yang terpisah tidak diwarisi.
Sarien
1
Harap pertimbangkan untuk menunjukkan dari mana m()asalnya, dan bagaimana itu akan berubah jika jenisnya misalnya int.
Deduplicator
Apakah mungkin untuk melakukan secara using Base::Baseimplisit? Ini akan memiliki konsekuensi besar jika saya lupa garis itu pada kelas turunan dan saya perlu mewarisi konstruktor di semua kelas turunan
Post Self
7

Tidak jelas apa yang Anda maksud dengan "mewarisi konstruktor induk". Anda menggunakan kata ganti , yang menunjukkan Anda mungkin berpikir tentang konstruktor yang berperilaku seperti fungsi virtual polimorfik . Saya sengaja tidak menggunakan istilah "konstruktor virtual" karena itu adalah nama umum untuk pola kode di mana Anda sebenarnya memerlukan instance objek yang sudah ada untuk membuat yang lain.

Ada sedikit utilitas untuk konstruktor polimorfik di luar pola "konstruktor virtual", dan sulit untuk membuat skenario konkret di mana konstruktor polimorfik yang sebenarnya mungkin digunakan. Contoh yang sangat dibuat-buat yang sama sekali tidak valid C ++ :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

Dalam hal ini konstruktor yang dipanggil tergantung pada jenis konkret variabel yang sedang dibangun atau ditugaskan. Sangat kompleks untuk mendeteksi selama parsing / pembuatan kode dan tidak memiliki utilitas: Anda tahu tipe konkret yang Anda buat dan Anda telah menulis konstruktor khusus untuk kelas turunan. Kode C ++ yang valid berikut melakukan hal yang persis sama, sedikit lebih pendek dan lebih eksplisit dalam hal apa yang dilakukannya:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Interpretasi kedua atau mungkin pertanyaan tambahan adalah - bagaimana jika konstruktor kelas Base secara otomatis hadir di semua kelas turunan kecuali disembunyikan secara eksplisit.

Jika anak mewarisi konstruktor orang tua, apa yang salah?

Anda harus menulis kode tambahan untuk menyembunyikan konstruktor induk yang tidak benar untuk digunakan dalam membangun kelas turunan. Ini bisa terjadi ketika kelas turunan mengkhususkan kelas dasar dengan cara parameter tertentu menjadi tidak relevan.

Contoh khas adalah persegi panjang dan kotak (Perhatikan bahwa kotak dan persegi panjang umumnya tidak dapat diganti Liskov sehingga bukan desain yang sangat bagus, tetapi menyoroti masalah ini).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Jika Square mewarisi konstruktor dua nilai Rectangle, Anda bisa membuat kotak dengan tinggi dan lebar yang berbeda ... Itu salah secara logis, jadi Anda ingin menyembunyikan konstruktor itu.

Joris Timmermans
sumber
3

Mengapa konstruktor tidak diwariskan: jawabannya sangat sederhana: Konstruktor kelas dasar "membangun" kelas dasar dan konstruktor kelas mewarisi "membangun" kelas warisan. Jika kelas yang diwarisi akan mewarisi konstructer, konstruktor akan mencoba untuk membangun objek kelas tipe dasar dan Anda tidak akan bisa "membangun" objek kelas tipe yang diwarisi.

Yang seperti itu mengalahkan tujuan mewarisi kelas.

Pieter B
sumber
3

Masalah yang paling jelas dengan memungkinkan kelas turunan untuk menimpa konstruktor kelas dasar adalah bahwa pengembang kelas turunan sekarang bertanggung jawab untuk mengetahui bagaimana membangun kelas dasar (es). Apa yang terjadi ketika kelas turunan tidak membangun kelas dasar dengan benar?

Juga, prinsip Substitusi Liskov tidak lagi berlaku karena Anda tidak lagi dapat mengandalkan koleksi objek kelas dasar Anda agar kompatibel satu sama lain, karena tidak ada jaminan bahwa kelas dasar dibangun dengan benar atau kompatibel dengan tipe turunan lainnya.

Lebih rumit lagi ketika lebih dari 1 level warisan ditambahkan. Sekarang kelas turunan Anda perlu tahu bagaimana membangun semua kelas dasar ke atas rantai.

Lalu apa yang terjadi jika Anda menambahkan kelas dasar baru ke bagian atas hierarki warisan? Anda harus memperbarui semua konstruktor kelas turunan.

Dunk
sumber
2

Konstruktor secara fundamental berbeda dari metode lain:

  1. Mereka dihasilkan jika Anda tidak menulisnya.
  2. Semua konstruktor kelas dasar dipanggil secara implisit bahkan jika Anda tidak melakukannya secara manual
  3. Anda tidak memanggil mereka secara eksplisit tetapi dengan membuat objek.

Jadi mengapa mereka tidak diwariskan? Jawaban sederhana: Karena selalu ada penggantian, baik dibuat atau ditulis secara manual.

Mengapa setiap kelas membutuhkan konstruktor? Itu adalah pertanyaan yang rumit dan saya percaya jawabannya tergantung pada kompiler. Ada sesuatu yang disebut sebagai konstruktor "sepele" di mana kompiler tidak mengamanatkan bahwa itu akan disebut tampaknya. Saya pikir itu adalah hal yang paling dekat dengan apa yang Anda maksud dengan pewarisan tetapi untuk tiga alasan yang disebutkan di atas saya pikir membandingkan konstruktor dengan metode normal tidak terlalu berguna. :)

Sarien
sumber
1

Setiap kelas membutuhkan konstruktor, bahkan yang standar.
C ++ akan membuat konstruktor default untuk Anda kecuali jika Anda membuat konstruktor khusus.
Jika kelas dasar Anda menggunakan konstruktor khusus, Anda harus menulis konstruktor khusus pada kelas turunan meskipun keduanya sama dan membuat rantai kembali.
C ++ 11 memungkinkan Anda untuk menghindari duplikasi kode pada konstruktor menggunakan menggunakan :

Class A : public B {
using B:B;
....
  1. Seperangkat konstruktor bawaan terdiri dari

    • Semua konstruktor non-templat dari kelas dasar (setelah menghilangkan parameter elipsis, jika ada) (karena C ++ 14)
    • Untuk setiap konstruktor dengan argumen default atau parameter ellipsis, semua tanda tangan konstruktor yang dibentuk dengan menjatuhkan ellipsis dan menghilangkan argumen default dari ujung daftar argumen satu per satu
    • Semua templat konstruktor dari kelas dasar (setelah menghilangkan parameter elipsis, jika ada) (karena C ++ 14)
    • Untuk setiap templat konstruktor dengan argumen default atau elipsis, semua tanda tangan konstruktor yang dibentuk dengan menjatuhkan ellipsis dan menghilangkan argumen default dari ujung argumen daftar satu per satu
  2. Semua konstruktor yang diwariskan yang bukan konstruktor default atau salin / pindahkan konstruktor dan yang tandatangannya tidak cocok dengan konstruktor yang ditentukan pengguna di kelas turunan, secara implisit dinyatakan dalam kelas turunan. Parameter default tidak diwarisi

MeduZa
sumber
0

Anda dapat gunakan:

MyClass() : Base()

Apakah Anda bertanya mengapa Anda harus melakukan ini?

Sub-kelas dapat memiliki properti tambahan yang mungkin perlu diinisialisasi dalam konstruktor, atau mungkin menginisialisasi variabel kelas dasar dengan cara yang berbeda.

Bagaimana lagi Anda membuat objek sub-tipe?

Tom Tom
sumber
Terima kasih. Sebenarnya, saya mencoba mencari tahu mengapa konstruktor tidak diwariskan di kelas anak, seperti metode kelas orang tua lainnya.
Suvarna Pattayil