1. #include <iostream>
    2. #include <string>
    3. using namespace std;
    4. class Base {
    5. public:
    6. int i;
    7. };
    8. class Son1 :public Base {};
    9. class Son2 :public Base {};
    10. class GrandSon : public Son1, public Son2 {};
    11. void example() {
    12. GrandSon s1;
    13. cout << s1.i << endl; // 由于菱形继承,这里会报错
    14. }
    15. int main() {
    16. example();
    17. return 0;
    18. }

    当然我们可以使用作用域的方法访问。因为这里实际上对象模型是包含了两个类的

    1. D:\C++\LearnCpp>cl /d1 reportSingleClassLayoutGrandSon LearnCpp.cpp
    2. 用于 x86 Microsoft (R) C/C++ 优化编译器 19.27.29112
    3. 版权所有(C) Microsoft Corporation。保留所有权利。
    4. LearnCpp.cpp
    5. class GrandSon size(8):
    6. +---
    7. 0 | +--- (base class Son1)
    8. 0 | | +--- (base class Base)
    9. 0 | | | i
    10. | | +---
    11. | +---
    12. 4 | +--- (base class Son2)
    13. 4 | | +--- (base class Base)
    14. 4 | | | i
    15. | | +---
    16. | +---
    17. +---
    18. LearnCpp.cpp(13): error C2385: 对“i”的访问不明确
    19. LearnCpp.cpp(13): note: 可能是“i”(位于基“Base”中)
    20. LearnCpp.cpp(13): note: 也可能是“i”(位于基“Base”中)

    但是可以使用虚继承
    继承前加virtual关键字后,变为虚继承 //此时公共的父类Animal称为虚基类

    1. class Base {
    2. public:
    3. int i = 0;
    4. };
    5. class Son1 :virtual public Base {};
    6. class Son2 :virtual public Base {};
    7. class GrandSon : public Son1, public Son2 {};
    8. void example() {
    9. GrandSon s1;
    10. cout << s1.i << endl;
    11. }
    12. int main() {
    13. example();
    14. return 0;
    15. }

    此时会正常输出零

    1. D:\C++\LearnCpp>cl /d1 reportSingleClassLayoutGrandSon LearnCpp.cpp
    2. 用于 x86 Microsoft (R) C/C++ 优化编译器 19.27.29112
    3. 版权所有(C) Microsoft Corporation。保留所有权利。
    4. LearnCpp.cpp
    5. class GrandSon size(12):
    6. +---
    7. 0 | +--- (base class Son1)
    8. 0 | | {vbptr}
    9. | +---
    10. 4 | +--- (base class Son2)
    11. 4 | | {vbptr}
    12. | +---
    13. +---
    14. +--- (virtual base Base)
    15. 8 | i
    16. +---
    17. GrandSon::$vbtable@Son1@:
    18. 0 | 0
    19. 1 | 8 (GrandSond(Son1+0)Base)
    20. GrandSon::$vbtable@Son2@:
    21. 0 | 0
    22. 1 | 4 (GrandSond(Son2+0)Base)
    23. vbi: class offset o.vbptr o.vbte fVtorDisp
    24. Base 8 0 4 0
    25. D:\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\ostream(285): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
    26. D:\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.27.29110\include\ostream(270): note: 在编译 模板 成员函数“std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)”时
    27. LearnCpp.cpp(13): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)”的引用
    28. LearnCpp.cpp(13): note: 查看对正在编译的 模板 实例化“std::basic_ostream<char,std::char_traits<char>>”的引用
    29. Microsoft (R) Incremental Linker Version 14.27.29112.0

    而它的对象模型也会变成一个虚拟指针
    vbptr - Virtual Base Pointer这个指针指向了一个vbtable
    vbtable - Virtual Base Table 这个值记录了一个偏移量,通过偏移量来找到唯一的值。
    这个值是对象的,而且只有一个,是公用的,思想类似于静态的成员变量。
    一个实例
    菱形继承概念:
    两个派生类继承同一个基类
    又有某个类同时继承者两个派生类
    这种继承被称为菱形继承,或者钻石继承
    典型的菱形继承案例:

    菱形继承问题:

    1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
    1. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

    示例:

    1. class Animal
    2. {
    3. public:
    4. int m_Age;
    5. };
    6. //继承前加virtual关键字后,变为虚继承
    7. //此时公共的父类Animal称为虚基类
    8. class Sheep : virtual public Animal {};
    9. class Tuo : virtual public Animal {};
    10. class SheepTuo : public Sheep, public Tuo {};
    11. void test01()
    12. {
    13. SheepTuo st;
    14. st.Sheep::m_Age = 100;
    15. st.Tuo::m_Age = 200;
    16. cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
    17. cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
    18. cout << "st.m_Age = " << st.m_Age << endl;
    19. }
    20. int main() {
    21. test01();
    22. system("pause");
    23. return 0;
    24. }

    总结:

    • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
    • 利用虚继承可以解决菱形继承问题