一、概念
C++允许一个类拥有多个基类,所以就有了多重继承。多重继承和单一继承很多方面都是类似的。
示例
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
virtual ~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda(){cout<<__func__<<endl;}
~panda(){cout<<__func__<<endl;}
};
上述代码中panda就是继承了bear和endanger
二、规则
1、多重继承的构造函数与析构函数
和单继承一样,子类对象是由每个基类和子类特有的部分共同组成的,上述代码中,子类panda对象的结构如下

当创建一个panda对象时,各个类构造函数和析构函数的执行顺序和单继承的规则一样:根据基类在派生列表中出现的顺序,依次进行构造,派生列表中第一个出现的是bear类,所以先创建bear对象,又发现bear是zooanimal的子类,所以先创建zooanimal的对象,然后创建bear对象。第二个出现在派生列表中的类是endangered,所以创建endangered类对象,最后创建panda中独有的部分,之后,整个panda对象生成完毕。析构函数的调用顺序和构造函数相反

上述代码只是隐式使用bear和endangered的构造函数,可以显示调用
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
~panda(){cout<<__func__<<endl;}
};
2.多重继承的拷贝构造与operator=
和单继承一样,如果不显示调用基类的拷贝构造,那么在拷贝初始化子类对象时,只会调用基类的构造函数
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
bear(const bear &t) {cout<<"bear(const bear &t)"<<endl;}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
panda(const panda &t){cout<<"panda(const panda &t)"<<endl;}
~panda(){cout<<__func__<<endl;}
};
int main(int argc, char const *argv[])
{
panda t;
panda t2=t;
return 0;
}

可见,上图中,只调用了子类对象的拷贝构造函数,而没有调用各个基类的拷贝构造函数
显示调用基类中的拷贝构造函数后,拷贝初始化子类对象时就会调用各个基类的拷贝构造
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}
~panda(){cout<<__func__<<endl;}
};

如果panda没有显示定义拷贝构造函数,那么在对panda对象进行拷贝初始化时,会使用编译器提供的拷贝构造函数,此时也会调用基类的拷贝狗构造函数,将上述代码的29行注释掉后,输出结果如下

可见,使用编译器提供的拷贝构造函数时,会调用子类的拷贝构造函数
operator=也是一样,如果各个基类显示定义了operator=,那么如果子类也定义了operator=,那么要在子类中显示调用基类的operator=,否则只会调用子类的operator=
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}
~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
bear &operator=(const bear &t) {cout<<__func__<<"bear"<<endl;return *this;}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
panda &operator=(const panda &t) {cout<<__func__<<"panda"<<endl;return *this;}
~panda(){cout<<__func__<<endl;}
};
int main(int argc, char const *argv[])
{
panda t;
panda t2;
t2=t;
return 0;
}

上述输出结构是子类定义了operator=,但是没有显示调用的结果,显示调用后的代码和输出结果如下
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}
~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}
bear &operator=(const bear &t) {
cout<<__func__<<"bear"<<endl;
zooanimal::operator=(t);
return *this;
}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}
panda &operator=(const panda &t) {
cout<<__func__<<"panda"<<endl;
bear::operator=(t);
endangered::operator=(t);
return *this;
}
~panda(){cout<<__func__<<endl;}
};

如果子类没有显示定义operator=,那么,一般情况,编译器也会自动生成一个operator=,此时对子类对象进行赋值时,也会调用基类的operator=,将上述代码的operator=注释掉的输出结果如下

3.多重继承的问题
和单一继承一样,因为多重继承,所以子类很多个基类,所以,子类对象可以切割转换成任意基类对象,任意基类的指针或引用都可以指向子类。但是可访问的成员依然是由对象,指针,和引用的静态类型决定
访问成员时,查找成员的顺序也和单一继承一样,见博客https://blog.youkuaiyun.com/Master_Cui/article/details/109849186
但是,因为有很多个基类,当不同基类中出现了同名函数时,会出现二义性错误
class zooanimal
{
public:
zooanimal(){cout<<__func__<<endl;}
~zooanimal(){cout<<__func__<<endl;}
};
class bear:public zooanimal
{
public:
bear(){cout<<__func__<<endl;}
void func() {cout<<__func__<<endl;}
~bear(){cout<<__func__<<endl;}
};
class endangered
{
public:
endangered(){cout<<__func__<<endl;}
void func() {cout<<__func__<<endl;}
~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
panda():bear(),endangered(){cout<<__func__<<endl;}
~panda(){cout<<__func__<<endl;}
};
int main(int argc, char const *argv[])
{
panda t;
t.func();
return 0;
}

上述代码中,endangered和bear都定义了func,所以,子类对象无法确定该调用哪个,所以出现二义性错误
t.bear::func();
此时,指定了func的作用域,指定调用bear中的func,此时不会出现二义性
void func() {cout<<__func__<<endl;}
此时也不会报错,因为查找func时,会先在子类panda中查找,找到了就不找了,所以不会报错
此外,和单一继承一样,不要让子类和基类中出现名字相同但形参列表不同的函数。示例见博客https://blog.youkuaiyun.com/Master_Cui/article/details/109849186
参考
《C++ Primer》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出
本文介绍了C++多重继承的概念、规则及问题。多重继承允许一个类有多个基类,其构造与析构函数、拷贝构造与operator=规则和单继承类似。但多重继承会出现二义性错误,可通过添加作用域或在子类实现同名函数避免。
533

被折叠的 条评论
为什么被折叠?



