一般来说,在派生类中对基类成员的访问是应该是唯一的。但是,由于在多继承的情况下,可能出现基类中某个成员的访问不唯一的情况,这称为对基类成员访问的二义性。
在多继承的情况下,通常有两种可能出现的二义性。
1 派生类的多个基类中调用其同名成员时可能出现二义性
class A
{
public:
void f(){}
};
class B
{
public:
void f() {}
void g() {}
};
class C : public A, public B
{
public:
void h() {}
};
如果定义一个类C的对象c, 则对函数f()的访问c.f()就会出现二义性,是访问A中的f,还是B中的f。
可以通过成员名限定发来消除二义性。例如:c.A::f() 或者 c.B::f()。
2 派生类有共同基类时访问公共基类成员可能出现二义性
class A
{
public:
int a;
};
class B1 : public A
{
public:
int b1;
};
class B2 : public A
{
public:
int b2;
};
class C : public B1, public B2
{
public:
int c;
};
如果定义了一个类C的对象c,则c.a 或者 c.A::a 都有问题。
正确的访问为c.B1::a 或者 c.B2::a。
对于第2中二义性,基类A被保存了2份。对C进行初始化时,类A会被初始化两次。 为了彻底避免在这种结构中的二义性,在创建派生类对象时对公共基类只进行一次初始化,则引入了虚继承和虚基类。
声明和定义:
虚继承的引入是为了解决二义性问题。其说明格式如下:
class <类名> : virtual <继承方式> <基类名>
其中,virtual是虚继承的关键字。
以虚继承方式被继承的基类称为虚基类。
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。
需要注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。
例如:
class A
{
public:
int a;
};
class B1 : virtual public A
{
public:
int b1;
};
class B2 : virtual public A
{
public:
int b2;
};
class C : public B1, public B2
{
public:
int c;
};
如果定义了类C的对象c,则c.a是正确的。并且,类C中只有一份类A的内容。
含有虚基类的子类的内存结构:
还以上面的代码为例,类C是如何实现,只包含一份类A的呢?在引入虚基类之后,虚基类的直接子类中就包含了一个指向虚基类的指针,这个指针称为虚基类指针。类B1和B2中各有一个指向类A的虚基类指针,类C中只继承了一份类A,但继承了类B1和B2中的指向类A的指针。
含虚基类的派生类的构造函数
为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类,由于派生类的对象中只有一个虚基类的子对象。为了保证虚基类子对象只被调用一次,这个虚基类构造函数必须只被调用一次。由于继承机构可能很深,规定将在建立对象时指定的类称为最派生类。C++语言规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。 而该派生类的直接基类中所列出的对这个虚基类的构造函数的调用在执行时被忽略,这样保证了对虚基类的子对象只初始化一次。
C++语言规定,在一个成员初始化列表中,对虚基类的初始化先于非虚基类的初始化。
class A
{
public:
A(int a) : a(a) {}
int a;
};
class B1 : virtual public A
{
public:
B1(int a,int b) : A(a), b1(b) {}
int b1;
};
class B2 : virtual public A
{
public:
B2(int a, int b) : A(a), b2(b) {}
int b2;
};
class C : public B1, public B2
{
public:
C(int a, int b1,int b2, int c) : A(a), B1(b1,b1), B2(b2,b2), c(c) {}
int c;
};
int main()
{
B1 b1(2,2);
cout<<b1.a<<endl;
B2 b2(3,3);
cout<<b2.a<<endl;
C c(1,2,3,4);
cout<<c.a<<endl;
}
结果: 2 3 1
在这段代码中,在类C中直接对类A进行初始化。并且,虽然类B1和类B2中都有对类A的初始化,但在对类C进行初始化时,B1和B2中对类A的初始化都被忽略了。这样就保证了类A只被初始化一次。