Mengapa jawaban lain?
Nah, banyak posting di SO dan artikel di luar mengatakan, bahwa masalah berlian diselesaikan dengan membuat satu instance, Abukan dua (satu untuk setiap induk D), sehingga menyelesaikan ambiguitas. Namun, ini tidak memberi saya pemahaman yang komprehensif tentang proses, saya berakhir dengan lebih banyak pertanyaan seperti
- bagaimana jika
Bdan Cmencoba untuk membuat contoh yang berbeda Amisalnya memanggil konstruktor parametrized dengan parameter berbeda ( D::D(int x, int y): C(x), B(y) {})? Contoh mana yang Aakan dipilih untuk menjadi bagian D?
- bagaimana jika saya menggunakan warisan non-virtual
B, tetapi untuk warisan virtual C? Apakah cukup untuk membuat satu contoh AdalamD ?
- haruskah saya selalu menggunakan warisan virtual secara default mulai sekarang sebagai tindakan pencegahan karena ini memecahkan masalah berlian yang mungkin terjadi dengan biaya kinerja yang kecil dan tidak ada kekurangan lainnya?
Tidak dapat memprediksi perilaku tanpa mencoba contoh kode berarti tidak memahami konsepnya. Di bawah ini adalah apa yang membantu saya memahami warisan virtual.
Ganda A
Pertama, mari kita mulai dengan kode ini tanpa warisan virtual:
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
// error: request for member 'getX' is ambiguous
//cout << "d.getX() = " << d.getX() << endl;
// error: 'A' is an ambiguous base of 'D'
//cout << "d.A::getX() = " << d.A::getX() << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
Mari kita lihat keluaran. Menjalankan B b(2);pembuatan A(2)seperti yang diharapkan, sama untuk C c(3);:
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);kebutuhan baik Bdan C, masing-masing dari mereka menciptakan sendiri A, jadi kita harus ganda Adi d:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
Itulah alasan untuk d.getX()menyebabkan kesalahan kompilasi karena kompiler tidak dapat memilih Ainstance mana yang harus dipanggil metode. Masih mungkin untuk memanggil metode secara langsung untuk kelas induk yang dipilih:
d.B::getX() = 3
d.C::getX() = 2
Virtualitas
Sekarang mari tambahkan warisan virtual. Menggunakan sampel kode yang sama dengan perubahan berikut:
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...
Mari melompat ke pembuatan d:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
Anda dapat melihat, Adibuat dengan konstruktor default mengabaikan parameter yang diteruskan dari konstruktor Bdan C. Saat ambiguitas hilang, semua panggilan untuk getX()mengembalikan nilai yang sama:
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
Tetapi bagaimana jika kita ingin memanggil konstruktor parametrized A? Ini dapat dilakukan dengan memanggilnya secara eksplisit dari konstruktor D:
D(int x, int y, int z): A(x), C(y), B(z)
Biasanya, kelas dapat secara eksplisit menggunakan konstruktor hanya dari orang tua langsung, tetapi ada pengecualian untuk kasus warisan virtual. Menemukan aturan ini "diklik" untuk saya dan sangat membantu memahami antarmuka virtual:
Kode class B: virtual Aberarti, setiap kelas yang diwarisi dari Bsekarang bertanggung jawab untuk membuat Asendiri, karena Btidak akan melakukannya secara otomatis.
Dengan mengingat pernyataan ini, mudah untuk menjawab semua pertanyaan yang saya miliki:
- Selama
Dpembuatan tidak Bjuga Cbertanggung jawab atas parameter A, itu sepenuhnya terserah Dsaja.
Cakan mendelegasikan pembuatan Ake D, tetapi Bakan membuat instance sendiri Asehingga mengembalikan masalah berlian
- Mendefinisikan parameter kelas dasar di kelas cucu daripada anak langsung bukanlah praktik yang baik, jadi harus ditoleransi ketika masalah intan ada dan ukuran ini tidak dapat dihindari.