14.1 多继承中的二义性问题
在派生类中对基类成员的访问应该是唯一的。但是在多继承情况下,可能造成对基类中某个成员的访问出现了不唯一的情况,这时就成对基类成员的访问产生了二义性。
class Base1
{
public:
void fun();
};
class Base2
{
public:
void fun();
};
class Dervied :public Base1, public Base2 {};
int main()
{
Dervied d;
d.fun();
return 0;
}
在d对象访问fun()函数时,由于无法确定访问的是那个函数会产生二义性。
要解决这个问题有两种方法:
(1)通过作用域符(::)明确指出访问的是基类那个函数。
如:d.Base1::fun();
(2)在类中定义同名成员。
class Dervied
{
public:
void fun(){Base1::fun();}
};
在类的继承层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用范围不同,是相互包含的两层,派生类在内层。这时如果派生类定义了一个和某个基类成员同名的新成员,派生的新成员就改写了外层同名成员,直接使用成员名只能访问到派生类的成员。
例1 分析下列程序的输出结果
#include<iostream>
using namespace std;
class Base1
{
public:
void fun() { cout << "Base1" << endl; }
int data;
};
class Base2
{
public:
void fun(){cout << "Base2" << endl;}
int data;
};
class Derived:public Base1,public Base2
{
public:
int data;
void fun(){ cout << "Derived" << endl; }
};
int main()
{
Derived obj;
obj.data = 1;
obj.fun();
obj.Base1::data = 2;
obj.Base1::fun();
obj.Base2::data = 3;
obj.Base2::fun();
return 0;
}
产生二义性问题的另外一中情况时当派生类从多个基类派生,而这些基类又有一个共同的基类,当共同的基类中说明的成员进行访问时,可能出现二义性。
由于二义性检测在访问控制权限或类型检查之前进行,因此访问控制权限不同或类型不同不能解决二义性问题。
13.2 虚基类的定义
当一个派生类从多个基类派生,而这些基类又有一个共同的基类,当对该基类中说明的成员进行访问时,可能出现二义性。虚基类就是为了解决这种二义性问题提出来的。
虚基类的说明格式如下:
class<类名>:virtual<继承方式><基类名>
其中关键字virtual与继承方式的位置无关,但必须位于虚基类名之前。且virtual只对紧随其后的基类名起作用。
13.3 虚基类的构造函数
使用虚基类解决二义性问题的关键是在派生类中只产生一个虚基类子对象。
为初始化基类子对象,派生类的构造函数要调用基类的构造函数。对于基类的对象中只有一个虚基类子对象,所以,在建立一个派生类的对象时,为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。虽然继承结构的层次可能很深,但要建立的对象所属的类只是这个继承结构中间的某个类,因此将在建立对象时所指定的类称为最派生类。虚基类子对象由最派生类的构造函数通过虚基类的构造函数实现。所以最派生类的构造函数初始化列表中必须列出对虚基类的构造函数的调用;如果为列出则表示使用该虚基类的默认构造函数
由于派生类总是相对的,因此,从虚基类直接或间接派生的派生类的构造函数的初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数才能调用虚基类的构造函数,此时最派生类的所有基类中列出的对虚基类的构造函数的调用在执行过程中都被忽略,从而保证对虚基类子对象只初始化一次。
当在构造函数的初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数。
例2 分析下列程序的输出结果
#include<iostream>
using namespace std;
class Base
{
public:
Base(char i);
~Base();
};
Base::Base(char i)
{
cout << "Base构造"<<i << endl;
}
Base::~Base()
{
cout << "Base析构" << endl;
}
class Derived1 :virtual public Base
{
public:
Derived1(char i, char j);
~Derived1();
};
Derived1::Derived1(char i, char j) :Base(i)
{
cout << "Derived1构造"<<j << endl;
}
Derived1::~Derived1()
{
cout << "Derived1析构" << endl;
}
class Derived12 :virtual public Base
{
public:
Derived12(char i, char j);
~Derived12();
};
Derived12::Derived12(char i, char j) :Base(i)
{
cout << "Derived12构造"<<j << endl;
}
Derived12::~Derived12()
{
cout << "Derived12析构" << endl;
}
class Derived2 :public Derived1,public Derived12
{
public:
Derived2(char i, char j,char k,char l,char n);
~Derived2();
private:
Base aa;
};
Derived2::Derived2(char i, char j, char k, char l, char n):Derived1(i,j),Derived12(k,l),Base(i),aa(n)
{
cout << "Derived2构造"<<n << endl;
}
Derived2::~Derived2()
{
cout << "Derived2析构" << endl;
}
int main()
{
Derived2 d('a', 'b', 'c', 'd', 'e');
return 0;
}
当同时出现虚基类和非虚基类构造函数的调用时,首先执行虚基类构造函数。
因此先执行Base的构造函数,再执行Derived1和Derived12的构造函数。
因此首先是Derived2对Base进行初始化,所以Derived1和Derived2不需要进行初始化。
参考《全国计算机等级考试二级教程——C++语言程序设计》