一:什么是C++多继承的多义性?
先看以下的代码:
class A
{
public:
int iValue;
};
class B1:public A
{
public:
void b1Printf() {cout<<"This is class B1"<<endl;};
};
class B2:public A
{
public:
void b2Printf() {cout<<"This is class B2"<<endl;};
};
class C:public B1,public B2
{
public:
void cPrintf() {cout<<"This is class C"<<endl;};
};
void main()
{
C c;
cout<<c.iValue<<endl; //错误,不明确的访问
cout<<c.A::iValue<<endl; //正确
cout<<c.B1::iValue<<endl; //正确
cout<<c.B2::iValue<<endl; //正确
}
分析:
如两个类B1,B2继承自同一个类A,然后又派生出同一个新类C.则用类C创建对象的objC访问父类A的成员,
就会出现多义性:即编译器无法确定访问的是通过类B1还是类B2的接口来访问类A的成员.
也就是说类B1, B2都继承了类A的iValue成员,因此类B1,B2都有一个成员变量iValue ,这样类C继承类B1,B2后就有一个重名的成员 iValue(一个是从类B1中继承过来的,一个是从类B2中继承过来的).在调用时编译器不知道调用从谁继承过来的iValue所以就产生的二义性的问题.
二:多继承的多义性的解决方法:
1.通过作用域分辨符::指明调用类B1,B2的iValue成员副本,此时类A可以不指定为虚基类.(此时创建类C的对象的话类A的构造函数只出现两次).
2.将在不同的派生路径中产生多个成员副本的基类设置为虚基类.此时在内存中,其成员就只有一个副本或者映射,解决了同名成员的唯一标识问题.
也就是说在继承的类A的前面加上virtual关键字表示被继承的类A是一个虚基类,它的被继承成员iValue在派生类B1,B2中只保留一个实例。(此时创建类C的对象的话类A的构造函数只出现一次)
两种方法的选择使用:
1.使用作用域分辨符时,基类成员在内存中有多个副本,可存放不同的数据,进行不同的操作.
2.使用虚基类技术,基类成员在内存中只有一个副本,更节约空间.
孰轻孰重,应酌情选择!
三:虚继承与虚基类:
先看代码:
class A
{
public:
int iValue;
};
class B1 : virtual public A
{
public:
void b1Printf(){cout<<"This is class B1"<<endl;};
};
class B2 : virtual public A
{
public:
void b2Printf(){cout<<"This is class B2"<<endl;};
};
class C:public B1,public B2
{
public:
void cPrintf(){cout<<"This is class C"<<endl;};
};
void main()
{
C c;
cout<<c.iValue<<endl; //正确
}
1.虚继承与虚基类的定义:
(1).虚继承(虚拟继承):在继承定义中包含了virtual关键字的继承关系;
(2).虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:struct CSubClass : virtual public CBase {}; 其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不是虚继承体系中的基类。
2.虚继承和虚基类的语法:
语法有语言的本身的定义所决定.
class CSubClass : virtual public CBaseClass {};
3.虚继承和虚基类的语义:
(1).关键字virtual的含义:
不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。
比如:虚函数必须要通过一种间接的运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种体制。存在,但间接。
(2).关键字virtual的特征:存在、间接和共享这三种特征。
对于虚函数而言,间接性表明了其必须在运行时根据实际的对象来完成函数寻址,共享性表现在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。
对于虚继承而言,存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成,共享性表现在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。
(3).关键字virtual的使用:
用于虚继承和虚函数两种情况.
(4),虚基类的初始化问题:
(1).派生类的构造函数,可以完成基类的初始化工作,虚基类也不例外.
(2).虚基类的初始化的特殊性只体现在其构造函数的调用顺序上:
1).同一层次中,虚基类的构造函数先于非虚基类的调用,而多个虚基类之间其构造函数调用次序于生命顺序有关.
2).不同层次之间,则按照"先基类,后自己"的次序调用.
(3).在包含虚基类的多层次继承中虚基类的初始化问题:
“为什么一旦出现了虚基类,就必须在每一个继承类中都必须包含虚基类的初始化语句”?
虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。
示例代码:
class A
{
public:
A( int i )
{
m_val = i;
}
int m_val;
};
/*
* 虚拟继承体系
*/
class B1 : virtual public A
{
public:
B1(int i) : A( i ) {}
};
class B2 : virtual public A
{
public:
B2(int i) : A( i ) {}
};
class C : public B1, public B2
{
public:
C( int i ) : A( i ), B1( i ), B2( i ) {}
};
class D : public C
{
public:
D( int i ) : A( i ), C( i ) {}
};
int main(int argc, char* argv[])
{
D objD(1);
cout << "objD.m_val = " << objD.m_val << endl;
return 0;
}