天天看点

【C++深度解析】38、被遗弃的多重继承(上)

文章目录

    • 1 何为多重继承
    • 2 多重继承问题一:对象拥有不同地址
    • 3 多重继承问题二:成员冗余
      • 3.1 虚继承解决数据冗余
    • 4 小结

1 何为多重继承

C++允许一个类拥有多个父类,这就是多重继承

  • 子类拥有所有父类的成员变量
  • 子类继承所有父类的成员函数
  • 子类对象可以当作任意父类对象使用

多重继承的语法规则:

【C++深度解析】38、被遗弃的多重继承(上)
// 38-1.cpp
#include<iostream>
using namespace std;
class BaseA
{
    int ma;
public:
    BaseA(int a) { ma = a; }
    int getA() { return ma; }
};

class BaseB
{
    int mb;
public:
    BaseB(int b) { mb = b; }
    int getB() { return mb; }
};

class Derived : public BaseA, public BaseB
{
    int mc;
public:
    Derived(int a, int b, int c) : BaseA(a), BaseB(b)
    {
        mc = c;
    }
    int getC() { return mc; }
    void print()
    {
        cout << "ma = " << getA() << ", "
            <<"mb = " << getB() << ", "
            << "mc = " << mc << endl;
    }
};
int main()
{
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    cout << "d.getA() = " << d.getA() << endl;
    cout << "d.getB() = " << d.getB() << endl;
    cout << "d.getC() = " << d.getC() << endl;
    cout << endl;

    BaseA* pa = &d;
    BaseB* pb = &d;

    cout << "pa->getA() = " << pa->getA() << endl;
    cout << "pb->getB() = " << pb->getB() << endl;
    cout << endl;

    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    return 0;
}
           
  • 类 Derived 继承了类 BaseA 和类 BaseB,子类是由父类成员叠加子类得到的,所以类 Derived 拥有成员变量 ma,mb,mc。计算类对象大小时,计算的是成员变量的大小,成员函数保存在代码段,不计入对象大小。所以类 Derived 大小为 12。
  • Derived 对象可以调用继承来的成员函数 getA() ,getB()
  • 子类对象可以转换为任意父类对象使用,所以程序第 46-50 行可以转换为两个父类对象,调用父类函数
  • 程序第 53-54行打印指向对象 d 的两个指针来说明多重继承的第一个问题

编译运行:

$ g++ 38-1.cpp -o 38-1
$ ./38-1
sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3

pa->getA() = 1
pb->getB() = 2

pa = 0x7ffee3d7e03c
pb = 0x7ffee3d7e040
           

运行结果和我们分析的一致,但是 pa,pb 都是指向对象 d 的指针,他们的值却不同。这就是第一个问题。

2 多重继承问题一:对象拥有不同地址

  • 通过多重继承得到的对象可能拥有“不同的地址”

如下图所示,两个指针指向一个对象,可能指向的位置不同,且目前没有解决方案。

【C++深度解析】38、被遗弃的多重继承(上)

3 多重继承问题二:成员冗余

  • 多重继承可能产生冗余的成员

如下所示,Teacher 和 Student 都是 People 的子类,Doctor 是 Teacher 和 Student 的子类。

【C++深度解析】38、被遗弃的多重继承(上)

我们用代码描述上述关系

// 38-2.cpp
#include<iostream>
using namespace std;
class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
        m_name = name;
        m_age = age;
    }
    void print()
    {
        cout << "Name = " << m_name << ", "
            << "Age = " << m_age << endl;
    }
};
class Teacher : public People
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};
class Student : public People
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};
class Doctor : public Teacher, public Student
{
    public:
    Doctor(string name, int age) : Teacher(name, age), Student(name, age)
    {
    }
};
int main()
{
    Doctor d("Tom", 29);
    //d.print();
    d.Teacher::print();
    d.Student::print();
    return 0;
}
           

由于 Teacher 和 Student 都是 People 的子类,所以 Teacher 和 Student 都有成员变量 m_name,int m_age,都有成员函数 print()。

Doctor 是 Teacher 和 Student 的子类,所以 Doctor 中有两个 m_name 变量,两个 m_age 变量,两个 print() 函数,我们使用 d.print() 调用时,编译器并不知道我们调用的是哪一个函数,需要用作用于限定符指明哪一个函数才行。

3.1 虚继承解决数据冗余

当多重继承关系出现闭合时产生数据冗余的问题

解决方案:虚继承

  • 虚继承能够解决数据冗问题
  • 中间层父类不再关心顶层父类的初始化
  • 最终子类必须直接调用顶层父类的构造函数
#include <iostream>
#include <string>
using namespace std;
class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
        m_name = name;
        m_age = age;
    }
    void print()
    {
        cout << "Name = " << m_name << ", "
             << "Age = " << m_age << endl;
    }
};

class Teacher : virtual public People				// 虚继承
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};

class Student : virtual public People				// 虚继承
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};

class Doctor : public Teacher, public Student
{
public:
	// 最终子类必须直接调用顶层父类的构造函数
    Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
    {
    }
};
int main()
{
    Doctor d("Tom", 33);
    d.print();
    return 0;
}
           

使用虚继承,最终子类直接调用顶层父类的构造函数,解决了数据冗余的问题。

$ g++ 38-2.cpp -o 38-2
$ ./38-2
Name = Tom, Age = 29
           

在架构设计时我们并不知道后面的代码会不会有多继承,所以我们并不知道是否应该使用虚继承,而且,需要直接调用顶层父类的构造函数,查找顶层父类效率低下。现在软件很少使用多重继承了。

4 小结

1、C++支持多重继承的编程方式

2、多重继承的问题

  • 可能出现同一个对象地址不同的情况
  • 虚继承可以解决数据冗余的问题
  • 虚继承使得架构设计可能出现问题

继续阅读