假设你正在进行一个项目,用软件来处理动物。只有两种动物:蜥蜴和鸡,如下是组织形式:
Animal class 将负责你所处理的所有动物的共同特征具现化,Lizard和Chicken class则分别将Animal特殊化为蜥蜴和鸡:
class Animal
{
public:
Animal& operator = (const Animal& rhs);
...
};
class Lizard: public Animal
{
public:
Lizard& operator = (const Lizard& rhs);
...
};
class Chicken: public Animal
{
Chicken& operator = (const Chicken& rhs);
...
};
考虑如下的代码:
Lizard liz1;
Lizard liz2;
Animal* pAnimal1 = &liz1;
Animal* pAnimal2 = &liz2;
...
*pAnimal1 = *pAnimal2;
上述代码有两个问题。第一,最后一行调用了Animal class的赋值操作,即使设计的对象是Lizard。导致部分赋值——只有“liz1的Animal成分”被修改。第二,通过指针对对象进行赋值,我们希望赋值动作的行为更加合理些。
一个解决的方法就是让assignment操作符成为虚函数,上述赋值动作就会正确:
class Animal
{
public:
virtual Animal& operator = (const Animal& rhs);
...
};
class Lizard: public Animal
{
public:
virtual Lizard& operator = (const Animal& rhs);
...
};
class Chicken: public Animal
{
virtual Chicken& operator = (const Animal& rhs);
...
};
但是这一规则强迫我们在每一个class中为此虚函数声明完全一样的参数类型。那就意味着Lizard和Chicken classes的assignment操作符必须接受“任何类型的Animal出现在赋值动作的右边”,导致必须面对一个事实——以下代码合法:
Lizard liz;
Chicken chick;
Animal* pAnimal1 = &liz;
Animal* pAnimal2 = &chick;
...
*pAnimal1 = *pAnimal2; //将一只鸡赋值给一只蜥蜴
我们希望同类型的可以赋值,异型的不能赋值。要区分这些必须在运行期才有办法,我们可以利用dynamic_cast协助完成任务,下面是Lizard的assignment操作符的做法:
Lizard& Lizard::operator = (const Animal& rhs)
{
const Lizard& rhs_liz = dynamic_cast<const Animal&>(rhs);
proceed with a normal assignment of rhs_liz to *this;
}
但是上述函数在平常情况下(将一个Lizard对象赋值给另一个Lizard对象)就没必要如此复杂和昂贵的代价。如果动用dynamic_cast就会动用一个type_info结构体。我们不需要dynamic_cast的复杂度和成本,我们依然可以处理这种情况,做法就是Lizard加上传统的assignment操作符(形成两个重载函数):
class Lizard: public Animal
{
public:
virtual Lizard& operator = (const Animal& rhs);
Lizard& operator = (const Lizard& rhs); //加上这行
};
Lizard liz1,liz2;
...
liz1 = liz2l //调用的const Lizard& 的operator=
Animal* pAnimal1 = &liz1;
Animal* pAnimal2 = &liz2;
...
*pAnimal1 = *pAnimal2;//调用的是const Animal&的operator=
事实上,有了第二个operator=之后,第一个operator可以简化:
Lizard& Lizard::operator = (const Animal& rhs)
{
return operator = dynamic_cast<const Animal&>(rhs);
}
这次我们将rhs转型为Lizard,如果成功就调用正常的operator操作符,否则抛异常。还有另外一种方法就是阻住clients一开始就做出有问题的赋值动作,让这种的动作在编译器就被拒绝,让operator=成为Animal的private函数:
Lizard liz1,liz2;
...
liz1 = liz2; //正确
Chicken chick1,chick2;
...
chick1 = chick2; //正确
Animal* pAnimal1 = &liz1;
Animal* pAnimal2 = &chick1;
...
*pAnimal1 = *pAnimal2; //错误,试图调用private
//Animal::operator=
不幸的是,Animal作为一个具体类,而上述却使得Anial独享彼此间的赋值动作也不合法:
Animal animal1,animal2;
...
animal1 = animal2; //错误,试图调用private
//Animal::operator=
此外,它也造成我们无法正确实现Lizard和Chicken的assignment操作符,因为derived的assignment操作符有义务调用base classes的assignment操作符:
Lizard& Lizard::operator = (const Lizard& rhs);
{
if(this == &rhs) return *this;
Animal::operator=(rhs); //错误,试图调用private
//Animal::operator=
...
}
将Animal::operator=声明为protected可以解决后一个问题,但前一个问题存在,阻止Lizard和Chicken对象之间部分赋值(通过Animal指针)的同时,我们必须允许Animal对象相互赋值。
解决的方法ji㐊以一个新的class为抽象类,然后再令每个具体类继承自抽象类,修改后的继承体系如下:
于是各个classes定义如下:
class AbstructAnimal
{
protected:
AbstructAnimal& operator=(const AbstructAnimal& rhs);
...
public:
virtual ~AbstructAnimal() = 0;
};
class Animal: public AbstructAnimal
{
public:
Animal& operator = (const Animal& rhs);
...
};
class Lizard: public AbstructAnimal
{
public:
Lizard& operator = (const Lizard& rhs);
...
};
class Chicken: public AbstructAnimal
{
public:
Chicken& operator = (const Chicken& rhs);
...
};
这个设计提供你需要的每一样东西。蜥蜴、鸡、动物之间允许同类型的赋值;部分赋值和异型赋值都在被禁止之列;derived的assignment操作符可以调用base的assignment操作符。
如果你有两个具体类C1和C2,而你希望C2以public方式继承自C1.你应该将原本的双类继承体系改成三类继承体系:产生一个抽象类A,并令C1和C2都以public继承A。
.
当你使用第三方厂商的各种C++类库的时候,如果你发现自己需要产生一个具体类,继承自成都库的一个具体类,而你zh能使用该程序库,不能修改,怎么办?下面是方法:
- 将你的具体类派生自既有的(程序库的)具体类,但需要注意本条款一开始所验证的assignment相关问题,还有多态数组的问题。
- 试着在程序库集成体系中找到更高的抽象类,其中有你需要的大部分功能,继承它。
- 以“你所希望继承的那个程序库类”来实现你的新类,相当于包含程序库的对象,has-a的关系。
- 做个乖宝宝,手上有什么就用什么。
准则是:集成体系中的non-leaf(非尾端)类应该是抽象类。如果你使用外界供应的程序库,你或许可以对此法则做点变通;但如果代码完全在你的掌控之下,坚持这个法则,可以为你带来许多的好处,并提升整个软件的可靠度、健壮度、精巧度、扩充度。