C++虛繼承時的構造函數的講解


在虛繼承中,虛基類是由最終的派生類初始化的,換句話說,最終派生類的構造函數必須要調用虛基類的構造函數。對最終的派生類來說,虛基類是間接基類,而不是直接基類。這跟普通繼承不同,在普通繼承中,派生類構造函數中只能調用直接基類的構造函數,不能調用間接基類的。

下面我們以菱形繼承為例來演示構造函數的調用:

  1. #include <iostream>
  2. using namespace std;
  3. //虛基類A
  4. class A{
  5. public:
  6. A(int a);
  7. protected:
  8. int m_a;
  9. };
  10. A::A(int a): m_a(a){ }
  11. //直接派生類B
  12. class B: virtual public A{
  13. public:
  14. B(int a, int b);
  15. public:
  16. void display();
  17. protected:
  18. int m_b;
  19. };
  20. B::B(int a, int b): A(a), m_b(b){ }
  21. void B::display(){
  22. cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
  23. }
  24. //直接派生類C
  25. class C: virtual public A{
  26. public:
  27. C(int a, int c);
  28. public:
  29. void display();
  30. protected:
  31. int m_c;
  32. };
  33. C::C(int a, int c): A(a), m_c(c){ }
  34. void C::display(){
  35. cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
  36. }
  37. //間接派生類D
  38. class D: public B, public C{
  39. public:
  40. D(int a, int b, int c, int d);
  41. public:
  42. void display();
  43. private:
  44. int m_d;
  45. };
  46. D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
  47. void D::display(){
  48. cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
  49. }
  50. int main(){
  51. B b(10, 20);
  52. b.display();
  53. C c(30, 40);
  54. c.display();
  55. D d(50, 60, 70, 80);
  56. d.display();
  57. return 0;
  58. }

運行結果:
m_a=10, m_b=20
m_a=30, m_c=40
m_a=50, m_b=60, m_c=70, m_d=80

請注意第 50 行代碼,在最終派生類 D 的構造函數中,除了調用 B 和 C 的構造函數,還調用了 A 的構造函數,這說明 D 不但要負責初始化直接基類 B 和 C,還要負責初始化間接基類 A。而在以往的普通繼承中,派生類的構造函數只負責初始化它的直接基類,再由直接基類的構造函數初始化間接基類,用戶嘗試調用間接基類的構造函數將導致錯誤。

現在采用了虛繼承,虛基類 A 在最終派生類 D 中只保留了一份成員變量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在調用 A 的構造函數時很有可能給出不同的實參,這個時候編譯器就會犯迷糊,不知道使用哪個實參初始化 m_a。

為了避免出現這種矛盾的情況,C++ 干脆規定必須由最終的派生類 D 來初始化虛基類 A,直接派生類 B 和 C 對 A 的構造函數的調用是無效的。在第 50 行代碼中,調用 B 的構造函數時試圖將 m_a 初始化為 90,調用 C 的構造函數時試圖將 m_a 初始化為 100,但是輸出結果有力地證明了這些都是無效的,m_a 最終被初始化為 50,這正是在 D 中直接調用 A 的構造函數的結果。

另外需要關注的是構造函數的執行順序。虛繼承時構造函數的執行順序與普通繼承時不同:在最終派生類的構造函數調用列表中,不管各個構造函數出現的順序如何,編譯器總是先調用虛基類的構造函數,再按照出現的順序調用其他的構造函數;而對於普通繼承,就是按照構造函數出現的順序依次調用的。

修改本例中第 50 行代碼,改變構造函數出現的順序

  1. D::D(int a, int b, int c, int d): B(90, b), C(100, c), A(a), m_d(d){ }

雖然我們將 A() 放在了最后,但是編譯器仍然會先調用 A(),然后再調用 B()、C(),因為 A() 是虛基類的構造函數,比其他構造函數優先級高。如果沒有使用虛繼承的話,那么編譯器將按照出現的順序依次調用 B()、C()、A()。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM