5.5 赋值兼容规则
赋值兼容规则是指在公有派生情况下,一个派生类的对象可用于基类的对象可以使用的地方。
例如:如果类derived从base类公有派生,则赋值兼容规则指的是下面三种情况。
(1)派生类的对象可以赋给基类的对象。
derived d;
base b;
b=d; //对象d中所含的b类成员被赋给b
(2)派生类的对象可以初始化基类的引用。
derived d;
base& br=d;
(3)派生类的对象的地址可以赋给指向基类的指针。
derived d;
base *pb=&d;
注意:通过pb或br只能访问对象d中所继承的基类的成员。
下面以指针为例,分析一下单一继承和多重继承中使用该规则的意义和应注意的问题。
5.5.1 单一继承的情况
继承为代码重用提供了一种简便的方法,无需修改基类的源代码,可以通过从基类派生派生一个新类来裁剪基类,以满足具体的应用要求,达到代码重用的目的。赋值兼容性规则拓展了代码重用的广度。
例EX_15.CPP可以求出屏幕上一个圆和一个椭圆之间的距离。
5.5.2多重继承的情况
多重继承可以视为多个单一继承的组合,因此,赋值兼容规则也适用于多重继承的情况。
设某多重继承的有向无环图(DAG)表示为图5—1:
并且C类是从A类和B类公有派生,则C类的对象可以用于A类或B类的对象可以使用的地方,例如:
C c;
A *pa=&c;
B *pb=&c;
并且可以使用强制类型转换将pa或pb置给指向C类对象的指针。如:
C *pc=(c *)pa;
但是,在派生类的基类又有共同的基类时,将指向派生类的指针强制为指向该共同基类的指针时会产生二义性。例如,对于图5—2的DAG:
分析下面的程序:
derived d;
derived *dptr=&d;
base *b;
b=dptr; //error
b=(base *)dptr; //error
当试图将一个指向derived类的指针强制转换成指向base类的指针时,编译器不知道是通过base1还是base2继承路径。为避免这种二义性,使用下面的语句:
b=(base *)(base1 *)dptr;//显示指明全路径
b=(base1 *)dptr; //强制到base1 *,然后从base1 *到base *进行隐式类型转换
上面的情况同样适用于函数调用中参数传递和返回值的情况。
多重继承比单一继承情况复杂的原因在于,派生类在这两种情况下在内存中的数据成员布局不一样。先分析单一继承情况下数据成员在内存中的布局。设有
class base
{
int b;
};
class derived:public base
{
float d;
}
则派生类derived的一个对象在内存中的布局如图5—3所示。
分析下面的程序:
derived d;
base *bptr=&d;//bptr指向图5—3中箭头A所指向的位置
derived *dptr=&d;//dptr指向图5—3中箭头A所指向的位置
如果派生类derived由下面的类等级建立:
class base
{
int b;
};
class base1:public base
{
int b1;
};
class base2:public base
{
int b2;
public:
void f2();
};
class derived:public base1,public base2
{
floadt d;
};
则派生类derived的一个对象在内存中的布局如图5—4所示。
和单一继承情况作比较,A位置由deerived *类型和base *类型的指针指向,而B位置由base2 *类型的指针指向,base2类的数据成员不再从开始位置A存放。
将derived类的对象d的地址置给base1 *类型和base2 *类型的指针时会产生不同的效果,即
base1 *bptr1=&d;//&d指向A位置,但bptr1指向A位置
base2 *bptr2=&d;// &d指向A位置,但bptr1指向B位置,编译器将调整&d的值为
//(char *)&d+sizeof(base1)
反过来,从指向base1类或base2类的指针转换成指向对应的base类的指针时,可分别指向A位置和B位置,但从derived *强制转换到base *时必须指定路径,否则会产生二义性。
一个base *类型的指针也不能直接转换成derived *类型的指针:
base *bptr;
derived *dptr;
dptr=(derived *)bptr;//error,不知道转换路径
dptr=(derived *)(base1 *)bptr;//correct,bptr的值不作调整
dptr=(derived *)(base2 *)bptr;//correct,bptr的值要调整为指向A的位置