Neden başka bir cevap?
Peki, SO'daki birçok gönderi ve dışarıdaki makaleler, elmas probleminin Aiki 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
Bve örneğin farklı parametrelerle ( ) parametrize kurucu çağırmanın Cfarklı örneklerini yaratmaya çalışırsa ? Hangi örneği parçası olmak için seçilecek ?AD::D(int x, int y): C(x), B(y) {}AD
- 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 Aiç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ı Bve Cbunların her biri kendi yaratarak, Abiz çift var bu yüzden, Aiç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 Avarsayılan yapıcı yapıcıları geçirilen parametreleri göz ardı oluşturulur Bve 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 Bartık Akendi başına oluşturmaktan sorumlu olduğu anlamına gelir, çünkü Bbunu otomatik olarak yapmayacaktır.
Bu ifade aklıma geldiğinde sahip olduğum tüm soruları yanıtlamak kolaydır:
DYaratılış sırasında ne parametrelerinden ne Bde Csorumlu değildir , Atamamen Dsadece kalmıştır .
Ciçin yaratma yetkisini Averecek D, ancak Bkendi örneğini yaratacak ve Abö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.