继承作为面向对象的基本特征之一,其使用率极高。不管是为了实现软件的基本功能,还是再程序的重构的过程中,我们总是会用到继承机制。正是因为其用途极为广泛,而且使用简单,大众程序员对其真正的内部实现机制的探究不是很深。而且,在大部分情况下,我们对继承的使用方法是错误的。下面用例子来说明问题。
class Animal {
public:
Animal &operator=(const Animal &rhs);
...
};
class Animal1: public Animal {
public:
Animal1 &operator=(const Animal1 &rhs);
...
};
class Animal2: public Animal {
public:
Animal2 &operator=(const Animal2 &rhs);
...
};
上面的代码只是简单的定义一个继承体系,即Animal作为基类,Animal1和Animal2公共继承它。三者都重载了赋值运算符。这对于问题的说明已经足够了。考虑下面的代码:
Animal1 an1;
Animal2 an2;
Animal *pAn1 = &an1;
Animal *pAn2 = &an2;
...
*pAn1 = *pAn2;
上面的代码足够简单了吧!问题就出现了。我们在最后一行的目的是将an2赋值给an1.如果你不是此目的,那么可以绕开本文了!因为通过指针,对对象进行赋值动作对于c++程序员来说,非常普遍。但是实际的效果确是,an1的Animal成分与an2的Animal成分相同,而an1的Animal1成本保持不变。这里提一下出现这种情况的原因:1.继承体系中的赋值函数是重载,而不是覆盖和隐藏(注意三者的区别:);2.由于Animal *pAn1 = &an1,是产生pAn1所覆盖的范围缩小的效果,因此当采用赋值操作时,实际上调用的赋值函数时基类的赋值函数。这种效果是不是导致你的an1对象二不象了,既不是原来的an1对x爱嗯,也不是你期待的an2对象。不过,如果你是想达到这种移花接木的效果,那么我恭喜你,这种用法太妙了,也说明你对c++ 的继承体系已经到了一种登峰造极的地步。
不过,大部分人都不是实现移花接木的功能,那么怎么实现全部成分的赋值效果呢?
既然已经用到了继承机制,那么就不得离开虚函数了。我们将赋值操作符函数定义为虚函数,代码如下:
class Animal {
public:
virtual Animal &operator=(const Animal &rhs);
...
};
class Animal1: public Animal {
public:
virtual Animal1 &operator=(const Animal1 &rhs);
...};class Animal2: public Animal { public: virtual Animal2 &operator=(const Animal2 &rhs);
...};
采用虚函数确实能够解决上面提到的全部成分的赋值效果,因为他会导致覆盖赋值函数,而不是上面的重载,因此会调用实际Animal1类的赋值函数。但这样仍然会带来问题,如下的代码:
[cpp] view plaincopy
Animal1 an1;
Animal2 an2;//这里是Animal2对象,与前面的Animal1不同
Animal *pAn1 = &an1;
Animal *pAn2 = &an2;
...
*pAn1 = *pAn2;//将Animal2对象赋值给Animal1
这样子会允许异型转换,明显还是会出现问题。如何解决呢?可以参考《More Effective c++》里面的条款34。