Neden başka bir cevap?
Peki, SO'daki birçok gönderi ve dışarıdaki makaleler, elmas probleminin A
iki yerine tek bir örnek oluşturarak çözüldüğünü söyler (her ebeveyn için bir tane D
), böylece belirsizliği çözer. Ancak, bu bana süreci kapsamlı bir şekilde anlamamı sağladı, daha da fazla soru ile karşılaştım.
- ne olur
B
ve örneğin farklı parametrelerle ( ) parametrize kurucu çağırmanın C
farklı örneklerini yaratmaya çalışırsa ? Hangi örneği parçası olmak için seçilecek ?A
D::D(int x, int y): C(x), B(y) {}
A
D
- için sanal olmayan kalıtım kullanıyorsam
B
, ancak sanal olanı kullanıyorsam C
? Bunun tek bir örneğini oluşturmak için yeterli mi A
içinde D
?
- Muhtemel elmas problemini düşük performans maliyeti ve başka sakıncaları olmadan çözdüğü için bundan böyle önleyici tedbir olarak her zaman varsayılan olarak sanal kalıtımı kullanmalı mıyım?
Kod örneklerini denemeden davranışı tahmin edememek, kavramı anlamamak demektir. Aşağıda, sanal mirasın etrafını sarmama yardımcı olan şey var.
Çift a
İlk olarak, sanal miras olmadan bu kodla başlayalım:
#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;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
Çıktıya geçelim. Yürütme B b(2);
, A(2)
beklendiği gibi oluşturur , aynı şekilde 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);
hem ihtiyacı B
ve C
bunların her biri kendi yaratarak, A
biz çift var bu yüzden, A
içinde d
:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
d.getX()
Derleyici hangi A
örnek için yöntemi çağıracağını seçemediği için derleme hatasına neden olmasının nedeni budur . Yine de seçilen üst sınıf için yöntemleri doğrudan çağırmak mümkündür:
d.B::getX() = 3
d.C::getX() = 2
Sanallık
Şimdi sanal miras ekleyelim. Aşağıdaki değişikliklerle aynı kod örneğini kullanma:
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl;
cout << "d.A::getX() = " << d.A::getX() << endl;
...
Şunların oluşturulmasına geçelim d
:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
Sen görebilirsiniz A
varsayılan yapıcı yapıcıları geçirilen parametreleri göz ardı oluşturulur B
ve C
. Belirsizlik ortadan kalktıkça, getX()
aynı değeri döndürmek için tüm çağrılar :
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
Peki ya parametrize kurucu için çağırmak istersek A
? Bunu yapıcıdan açıkça çağırarak yapılabilir D
:
D(int x, int y, int z): A(x), C(y), B(z)
Normalde, sınıf yalnızca doğrudan ebeveynlerin oluşturucularını kullanabilir, ancak sanal miras durumu için bir dışlama vardır. Bu kuralı keşfetmek benim için "tıklandı" ve sanal arayüzlerin anlaşılmasına çok yardımcı oldu:
Kod class B: virtual A
, miras alınan herhangi bir sınıfın B
artık A
kendi başına oluşturmaktan sorumlu olduğu anlamına gelir, çünkü B
bunu otomatik olarak yapmayacaktır.
Bu ifade aklıma geldiğinde sahip olduğum tüm soruları yanıtlamak kolaydır:
D
Yaratılış sırasında ne parametrelerinden ne B
de C
sorumlu değildir , A
tamamen D
sadece kalmıştır .
C
için yaratma yetkisini A
verecek D
, ancak B
kendi örneğini yaratacak ve A
böylece elmas problemini geri getirecek
- Direkt çocuk yerine torun sınıfında temel sınıf parametrelerini tanımlamak iyi bir uygulama değildir, bu nedenle elmas problemi olduğunda ve bu önlem kaçınılmaz olduğunda buna tolerans gösterilmelidir.