Mengapa jawaban lain?
Nah, banyak posting di SO dan artikel di luar mengatakan, bahwa masalah berlian diselesaikan dengan membuat satu instance, A
bukan 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
B
dan C
mencoba untuk membuat contoh yang berbeda A
misalnya memanggil konstruktor parametrized dengan parameter berbeda ( D::D(int x, int y): C(x), B(y) {}
)? Contoh mana yang A
akan dipilih untuk menjadi bagian D
?
- bagaimana jika saya menggunakan warisan non-virtual
B
, tetapi untuk warisan virtual C
? Apakah cukup untuk membuat satu contoh A
dalamD
?
- 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 B
dan C
, masing-masing dari mereka menciptakan sendiri A
, jadi kita harus ganda A
di 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 A
instance 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, A
dibuat dengan konstruktor default mengabaikan parameter yang diteruskan dari konstruktor B
dan 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 A
berarti, setiap kelas yang diwarisi dari B
sekarang bertanggung jawab untuk membuat A
sendiri, karena B
tidak akan melakukannya secara otomatis.
Dengan mengingat pernyataan ini, mudah untuk menjawab semua pertanyaan yang saya miliki:
- Selama
D
pembuatan tidak B
juga C
bertanggung jawab atas parameter A
, itu sepenuhnya terserah D
saja.
C
akan mendelegasikan pembuatan A
ke D
, tetapi B
akan membuat instance sendiri A
sehingga 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.