多重继承是C ++的一个特性,其中类可以从多个类继承。C++中,所有的继承都是实现继承,只继承接口是不可能的。就是使用private继承和protected继承,派生类还是包含了所有基类的数据成员,只有对基类的私有成员有访问限制而已。
C++模拟接口继承的方式是,通过从一个“接口类”派生。接口类是一个不包含数据和函数体的类,意味着它除了析构函数以外,都是纯虚函数(纯虚析构函数也需要函数体,即使不包含代码)。
一般来说使用模板可以解决多重继承的问题。
实现多重继承
继承类的构造函数按照它们继承的顺序进行调用。例如,在以下程序中,B的构造函数在A的构造函数之前调用。
#include<iostream>
using namespace std;
class A
{
public:
A() { cout << "A's constructor called" << endl; }
};
class B
{
public:
B() { cout << "B's constructor called" << endl; }
};
class C: public B, public A // Note the order
{
public:
C() { cout << "C's constructor called" << endl; }
};
int main()
{
C c;
return 0;
}
Output:
B's constructor called
A's constructor called
C's constructor called
析构函数以构造函数相反的顺序调用。
菱形继承问题
当类的两个超类具有公共基类时,会出现菱形继承问题。例如,在下图中,TA类获取人类所有属性的两个副本,在向上类型转换时,有两个子对象选择,导致二义性。对于只在祖父类定义的函数,调用同样会产生二义性。
#include<iostream>
using namespace std;
class Person {
// Data members of person
public:
Person(int x) { cout << "Person::Person(int ) called" << endl; }
};
class Faculty : public Person {
// data members of Faculty
public:
Faculty(int x):Person(x) {
cout<<"Faculty::Faculty(int ) called"<< endl;
}
};
class Student : public Person {
// data members of Student
public:
Student(int x):Person(x) {
cout<<"Student::Student(int ) called"<< endl;
}
};
class TA : public Faculty, public Student {
public:
TA(int x):Student(x), Faculty(x) {
cout<<"TA::TA(int ) called"<< endl;
}
};
int main() {
TA ta1(30);
}
Output:
Person::Person(int ) called
Faculty::Faculty(int ) called
Person::Person(int ) called
Student::Student(int ) called
TA::TA(int ) called
在上述程序中,“person”的构造函数被调用为两次。 “person”的析构函数也会在对象“ta1”被析构时被调用两次。所以“ta1”对象有”Person”的所有成员的两个副本,这就造成了歧义,编译器会报错。
引入“virtual”关键字消除重复子对象
要想解决重复父类副本的问题,需要实现真正的菱形继承,也就是说,子对象要共享共同父类对象。具体实现,可以使用virtual public继承。
我们将班级的“Faculty”和“Student”作为虚基类,以避免“TA”类中有的“Person”两个副本。例如,考虑以下程序。
#include<iostream>
using namespace std;
class Person {
public:
Person(int x) { cout << "Person::Person(int ) called" << endl; }
Person() { cout << "Person::Person() called" << endl; }
};
class Faculty : virtual public Person {
public:
Faculty(int x):Person(x) {
cout<<"Faculty::Faculty(int ) called"<< endl;
}
};
class Student : virtual public Person {
public:
Student(int x):Person(x) {
cout<<"Student::Student(int ) called"<< endl;
}
};
class TA : public Faculty, public Student {
public:
TA(int x):Student(x), Faculty(x) {
cout<<"TA::TA(int ) called"<< endl;
}
};
int main() {
TA ta1(30);
}
Output:
Person::Person() called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called
在上述程序中,“Person”的构造函数被调用一次。在上面的输出中要注意的一个重要事情是,调用“Person”的默认构造函数。当我们使用’virtual’关键字时,默认情况下调用祖父类的默认构造函数,即使父类显式调用带参数构造函数。
那如何调用“Person”类的带参数构造函数?
构造函数必须在“TA”类中调用。例如,
#include<iostream>
using namespace std;
class Person {
public:
Person(int x) { cout << "Person::Person(int ) called" << endl; }
Person() { cout << "Person::Person() called" << endl; }
};
class Faculty : virtual public Person {
public:
Faculty(int x):Person(x) {
cout<<"Faculty::Faculty(int ) called"<< endl;
}
};
class Student : virtual public Person {
public:
Student(int x):Person(x) {
cout<<"Student::Student(int ) called"<< endl;
}
};
class TA : public Faculty, public Student {
public:
TA(int x):Student(x), Faculty(x), Person(x) {
cout<<"TA::TA(int ) called"<< endl;
}
};
int main() {
TA ta1(30);
}
Output:
Person::Person(int ) called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called
一般来说,不允许直接调用祖父的构造函数,必须通过父类调用。仅当使用’virtual’关键字时才允许。
#include<iostream>
using namespace std;
class A
{
int x;
public:
void setX(int i) {x = i;}
void print() { cout << x; }
};
class B: public A
{
public:
B() { setX(10); }
};
class C: public A
{
public:
C() { setX(20); }
};
class D: public B, public C {
};
int main()
{
D d;
d.print();
return 0;
}
Output:
error: request for member 'print' is ambiguous
d.print();
如果class B和class C 继承时,加上virtual关键字则输出20.因为按照多个父类出现的先后顺序构造,C在B之后构造。
#include<iostream>
using namespace std;
class A
{
int x;
public:
A(int i) { x = i; }
void print() { cout << x; }
};
class B: virtual public A
{
public:
B():A(10) { }
};
class C: virtual public A
{
public:
C():A(10) { }
};
class D: public B, public C {
};
int main()
{
D d;
d.print();
return 0;
}
Output:
error: no matching function for call to 'A::A()'
如果为A添加一个默认构造函数,则编译通过。
名称查找问题
对于上图继承体系,假设Left和Right中都有一个名称为f的函数,则类Bottom继承了两个同名函数,消除二义性调用的方法是,用基类名限定函数的调用,如using Left::f。
一个派生类的作用域被认为嵌套在基类的作用域之内,这在同名函数之间的选择时,编译器选择更高派生层次内的函数。
何时使用多重继承
1、需要通过新类来显式两个类的公共接口
2、需要向上类型转换成两个基类类型。任何时候,如果将新类的一个对象作为参数传递给某个期盼嵌入对象的函数,这就需要类型转换函数。
如果不必需要向上类型转换为所有基类类型,则应该像避免使用goto语句一样避免使用多重继承。