跳至主要內容

多基继承

张威大约 4 分钟c/c++继承

多基继承

形式

class 派生类 
: public/protected/private 基类1
, ...
, public/protected/private 基类N
{
    
};

class不写出来就是私有继承

class D 
: public A, B, C
{
    
};

注意上面的写法中B和C属于私有继承,D的实例化的对象是无法访问B、C的成员方法的

class D 
: public A
, public B
, public C
{
    
};

构造和析构顺序

派生类构造函数初始化列表不写时,构造和析构顺序

class D 
: public A
, public B
, public C
{
public:
	D(){
        cout << "D()" << endl;
    }
    
    ~D(){
        cout << "~D()" << endl;
    }
};

构造函数调用顺序和派生列表一致

派生类初始化列表顺序和派生列表顺序不一致时

class D 
: public A
, public B
, public C
{
public:
	D()
	: A()
	, C()
	, B()
	{
        cout << "D()" << endl;
    }
    
    ~D(){
        cout << "~D()" << endl;
    }
};

顺序依然没变

小结

多基继承和单基继承的派生类构造函数完成的任务和执行顺序并没有本质不同,唯一一点区别在于:首先要执行所有基类的构造函数,再执行派生类构造函数中初始化表达式的其他内容和构造函数体。各基类构造函数的执行顺序与其在初始化表中的顺序无关,而是由定义派生类时指定的基类顺序(派生列表)决定的。

成员名冲突的二义性

一般来说,在派生类中对基类成员的访问应当具有唯一性,但在多基继承时,

如下面的例子,我们先定义3个不同的类A、B、C,这3个类中都有一个同名成员函数print,然后让类D继承自A、B、C,则当创建D的对象d,用d调用成员函数print时,发生编译错误。

class A 
{
public:
    void print()
    {   
        cout << "A::print()" << endl;   
    }
};

class B
{
public:
    void print()
    {   
        cout << "B::print()" << endl;   
    }
};

class C 
{
public:
    void print() 
    {   
        cout << "C::print()" << endl;   
    }
};

class D 
: public A
, public B
, public C
{
    
};

void test() 
{
    D d;
    d.print();//error
    d.A::print();//ok
    d.B::print();//ok
    d.C::print();//ok
}

解决该问题的方式比较简单,只需要在调用时,指明要调用的是某个基类的成员函数即可,即使用就可以解决该问题

菱形继承的二义性问题

多基派生中,如果在多条继承路径上,如下图所示,不难看出,在D类对象中,会有来自两条不同路径的共同基类(类A)的双重拷贝。

class A 
{
public:
    void setNumber(long number)
    {   
        _number = number;
    }
    
private:
    long _number;
};
class B
: public A
{
    
};
class C
: public A
{
    
};
class D
: public B
, public C
{
    
};
  • D不能直接调用A的成员

    D d;
    d.A::print();	//error
    
  • 可以加作用域访问B或C中的成员

    d.B::print();
    d.C::print();
    
  • D实例化对象会调用A的构造函数和析构函数,因此A的成员在D实例化的对象的内存中存在两份,会产生二义性

出现这种问题时,我们的解决方案是采用****,。中间的类B、C虚拟继承自A,就可以解决了。至于背后到底发生了什么,待我们学了多态的知识后一起做讲解。

class A 
{
public:
    void setNumber(long number)
    {   
        _number = number;
    }
    
private:
    long _number;
};

class B
: virtual public A
{
    
};

class C
: virtual public A
{
    
};

class D
: public B
, public C
{
    
};

int main(void)
{
    D d;
    d.setNumber(10);
    
    return 0;
}
  • D实例化对象会调用A的构造函数和析构函数
  • D继承B、C的虚基指针,只有A类的成员