菱形继承问题

文章前半部分引用:http://blog.youkuaiyun.com/icerlion/article/details/4409618

考虑这种情况:

class CA{};

class CB : public CA{};

class CC : public CA{};


这时我们需要一个CD类,它需要同时继承CB和CC。

问题就出现了。

如果我们这样写:

class CD : public CB, public CC{};

 

那么我们在构造一个CD类的时候,

其结果如下:

一个孙子有两个爷爷

 

即:一个CD类中有两个CA(它的超类的对象)

这样的情况是不合理的。此时会出现模糊调用的现象。

 

如何避免这种现象呢?

虚继承就有了用武之地。

 

我们需要改写CB和CC的继承方式。

class CB : public virtuel CA{};

class CC : public virtual CA{};

 

其他无需修改。此时我们构造一个CD类的时候,

其顺序如下:

开始构造CD类,

先构造一个超类CA,然后构造CB,

在构造CC(此时不会构造CC的父类CA)

这样一来,

CD类对象中包含的CB和CC将共享同一份CA对象。

 一个孙子就只有一个爷爷

这时就不会出现模糊调用的现象了。

 

这个就是C++臭名昭著的多继承。

在JAVA中根本不会出现这种情况,

如果必须使用菱形继承的时候务必使用虚拟继承。

如果你的项目中有太多的菱形继承,

你或许应该重新考核一下你的软件工程师。

--------------------------------------------------------------------------

例子:

fun.h

#ifndef FUN_H
#define FUN_H

class A
{
public:
    int a;
    A()
    {
        a=10;
    }
};

class B1:public A //没有使用虚继承
{
public:
    int b1;
    B1()
    {
        b1=1;
        a=11;
    }
};

class B2:public A //没有使用虚继承
{
public:
    int b2;
    B2()
    {
        b2=2;
        a=12;
    }
};

class C:public B1,public B2
{
public:
    int c;
    C()
    {
        c=3;
    }
};
#endif



// main.cpp

#include "fun.h"
#include <iostream>
using namespace std;

int main()
{
    C *pc=new C();
    cout<<pc->a<<endl;
    return 0;
}


编译不通过,提示A的数据成员a为模糊调用;

main.cpp: In function ‘int main()’:
main.cpp:25: error: request for member ‘a’ is ambiguous
fun.h:24: error: candidates are: int A::a
fun.h:24: error:                 int A::a
make: *** [all] 错误 1


当将fun.h中B1、B2改为虚拟继承A之后则可编译通过;

class B1:virtual public A

class B2:virtual public A


同时运行结果将为12,

root@debian6:/home/michael/cppProject/angleInheritance# make
g++ -c  main.cpp
g++ -o t main.o
rm *.o
root@debian6:/home/michael/cppProject/angleInheritance# ./t
12
结果12的原因是C类 构造函数C()会先调用虚基类A的构造函数A(),然后再调用自身父类构造函数B1(),B2(),调用顺序是根据“class C:public B1,public B2”从左向右依次进行。


若将C的继承顺序改为 class C:public B2,public B1”后,结果则为11

root@debian6:/home/michael/cppProject/angleInheritance# make
g++ -c  main.cpp
g++ -o t main.o
rm *.o
root@debian6:/home/michael/cppProject/angleInheritance# ./t
11







<think>我们正在讨论面向对象编程中的多重继承,特别是菱形继承问题。用户的问题集中在“什么是菱形继承问题”以及它在多重继承中的表现。根据用户提供的引用,我们可以总结如下: 引用[2]和引用[3]都描述了菱形继承问题的核心:当一个类D同时继承自两个类B和C,而B和C又都继承自同一个基类A时,如果A中有一个方法被B和C分别重写,那么D调用该方法时就会产生二义性:到底应该调用B的方法还是C的方法? 此外,引用[4]和引用[5]提到了C++中解决菱形继承问题的方法:虚继承(virtual inheritance)。通过虚继承,可以确保在菱形继承结构中,最终派生类只包含一份基类子对象,从而避免二义性和数据冗余。 因此,我们的回答应该包括: 1. 菱形继承问题的定义和结构(用类图表示)。 2. 菱形继承导致的两个主要问题:二义性和数据冗余。 3. 解决方法(以C++的虚继承为例)及其原理(虚基类指针和虚基类表)。 4. 其他语言(如Java)如何避免菱形继承问题(使用接口)。 同时,我们还需要注意Python中的多重继承情况(因为用户之前的问题是关于Python的),但本次问题更偏向于一般性的面向对象概念。 回答结构: 1. 定义:什么是菱形继承? 2. 问题:二义性和数据冗余。 3. 解决方案:虚继承(C++)和接口(Java)。 4. Python中的情况(因为用户之前的问题涉及Python,但本次问题没有特别指明语言,所以简要提一下Python的MRO机制)。 最后,按照要求,生成相关问题。</think>### 面向对象编程中的菱形继承问题 #### 1. 什么是菱形继承问题菱形继承(Diamond Inheritance)是多重继承中的经典问题,发生在以下类结构中: ```mermaid classDiagram A <|-- B A <|-- C B <|-- D C <|-- D class A { +data } class B { } class C { } class D { } ``` - **结构特征**:类D同时继承自B和C,而B和C又都继承自同一个基类A - **核心问题**:当D访问A的成员时,存在两条继承路径(D→B→A 和 D→C→A),导致二义性和内存冗余[^2][^5] #### 2. 菱形继承引发的两大问题 1. **二义性问题(Ambiguity)** - 当A的方法在B和C中被不同方式重写时,D无法确定调用哪个版本 ```cpp class A { public: void show() { cout << "A"; } }; class B : public A { public: void show() { cout << "B"; } }; class C : public A { public: void show() { cout << "C"; } }; class D : public B, public C {}; D d; d.show(); // 错误:对'show'的调用不明确 ``` 2. **内存冗余问题(Redundancy)** - D会包含**两份A的实例**(通过B继承一份,通过C继承一份) ```cpp class A { int data[100]; }; // 占用400字节 class B : public A {}; class C : public A {}; class D : public B, public C {}; sizeof(D); // 结果约800字节(包含两份A的数据) ``` #### 3. 解决方案:虚继承(C++示例) C++通过**虚继承(virtual inheritance)** 解决菱形问题: ```cpp class A { public: int data; }; // 基类 // 虚继承声明 class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C { public: using B::data; // 显式指定使用B路径的data }; int main() { D d; d.data = 10; // ✅ 无二义性 cout << sizeof(d); // 大小≈sizeof(A)+指针开销[^4][^5] } ``` **虚继承原理**: 1. 虚基类指针:B和C各增加一个虚基类指针(通常4字节) 2. 共享实例:D只保留**一份A的实例**,B和C通过虚指针访问 3. 内存优化:牺牲8字节指针空间,避免数百字节冗余[^5] #### 4. 其他语言的解决方案 1. **Java/Go**:完全禁用多重继承,通过接口实现类似功能 ```java interface Pet { void play(); } // 接口无数据成员 class Dog extends Animal implements Pet { // 单继承+多接口 public void play() { ... } // 强制实现方法 } ``` - 优点:杜绝二义性和冗余[^3] - 缺点:无法复用基类实现 2. **Python**:通过方法解析顺序(MRO)解决 ```python class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(D.mro()) # 输出: [D, B, C, A, object] ``` - 按MRO顺序搜索方法(广度优先) - 使用`super()`调用父类方法 #### 5. 最佳实践建议 1. **优先使用组合**:用"has-a"替代"is-a"关系 2. **接口分离**:Java风格接口优于多重继承 3. **谨慎使用虚继承**:C++中仅当必须共享基类状态时使用 4. **明确调用路径**:如C++的`using B::data`指定路径[^4] > 菱形继承的本质是**继承路径收敛**导致的状态冲突,合理使用虚继承、接口或组合方案可平衡灵活性与复杂性[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值