您的位置:首页 > 其它

条款40:明智而谨慎地使用多重继承

2009-08-21 11:26 337 查看
条款40:明智而谨慎地使用多重继承
Use multiple inheritance judiciously.

内容:
当我们提到多重继承(multiple inheritance:MI)时,就不得不面对当前C++的两个基本阵营:一个认为单一继承SI(single inheritance) 是好的,MI一定更好!令一个阵营认为,SI是好的,但MI不值得我们去频繁使用.本款我们就带领大家来了解一下关于MI的两个基本观点.
先看下面这个例子:
class BorrowableItem{ //图书馆允许你借出的东西
public:
void checkOut(); //离开时进行检查
...
};
class ElectronicGadget{ //电子机电
private:
bool checkOut()const;//执行监测,返回监测结果
...
};
//MP3Player同时继承BorrowableItem与ElectronicGadget,(图书馆可以借出MP3 player)
class MP3Player:public BorrowableItem,public ElectronicGadget{ ...};
//下面我们来测试上面类的功能,看是否完整
MP3Player player;
player.checkOut(); //哒哒哒哒,问题来了:你说哪个checkOut将被调用?
注意这里对checkOut的调用产生了歧义.你现在一定说,这怎么可能?BorrowableItem::checkOut为public, ElectronicGadget::checkOut为private,测试代码中直接调用的版本肯定只有BorrowableItem::checkOut嘛!这种正常的思考模式看起来还不错.不过我的解释是,函数调用是否产生歧义取决于C++对重载函数的调用规则的解析,这里的规则是:C++总是在找出调用的最佳匹配函数之后,才开始对该函数"是否可调用"进行判断!对于这个例子,就是说,本例子中的两个checkOut都有着相同的匹配程度,所以编译器不能确定所谓的最佳匹配,于是抛出"调用产生歧义"的错误.做完了这一步之后就结束检查,当然不会对函数的可调用性进行审查.呵呵!
当然你可以明确的指出调用哪一个base class内的函数来解决这个歧义:
player.BorrowableItem::checkOut();
你也可以尝试调用另外一个版本的checkOut:
player.ElectronicGadget::checkOut();
显然你会获得一个"尝试调用private成员函数"的错误.
当多重继承中的两个或多个子类同时继承相同的基类的时候,如果用普通的public继承体系的话,就出现最顶层基类数据被重复复制的问题,于是就出现了virtual base继承来解决这个问题,我想大家都很熟悉它的便利了,在这个时候就有了一个简单的规则:在多重继承体系中,如果你使用public继承,请改用virtual public继承.从正确性来看,这个规则很不错.但正确性并不是唯一的观点.你必须考虑一下后果:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们的体积还要大.访问virtual base class的成员变量时,比访问non-virtual base class的成员变量速度慢.很明显:你得为virtual继承付出代价.
virtual继承的成本还不止上面提到的,具体的你可以自己仔细的思考一下,在这里我们给出了一些忠告:(1)非必要不要使用virtual base,平常请使用non-virtual继承.(2)如果你必须使用virtual base class,尽可能避免在其中放置数据.
下面我们来看看下面这个IPerson接口类:
class IPerson{
public:
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
我们要想得到IPerson接口,就必须实例化该类,条款31中我们用factory function去创建"派生自IPerson的对象":
//根据数据库id创建一个Person对象
std::tr1::shared_ptr<IPerson> makePerson( DatabaseID personIdentifier );
//取得一个数据库id
DatabaseID askUserForDatabaseID();
//执行代码
DatabaseID id( askUserForDatabaseID() );
std::tr1::shared_ptr<IPerson> person( makePerson(id) );
...
我们知道,makePerson内部肯定创建了一个派生自IPerson的具体类,我们暂且将它称为CPerson,该类必须独自实现接口中的方法.但在这里我们已经有了一个PersonInfo类,该类提供了CPerson所需要的东西:
class PersonInfo{
public:
using std::string;
explicit PersonInfo(DatabaseID personID);
virtual ~PersonInfo();
virtual const string theName()const;
virtual const string theBirthDate()const;
...
private:
//返回左界限符
virtual const string valueDelimOpen()const;
//返回右界限符
virtual const string valueDelimClose()const;
};
以代码实现起来可能像这样:
const string PersonInfo::valueDelimOpen()const{
return "["; //'['并不是每个人都喜欢的,子类可以重新改写实现
}
const string PersonInfo::valueDelimClose()const{
return "]";//']'并不是每个人都喜欢的,子类可以重新改写实现
}
const string PersonInfo::theName()const{
string value = valueDelimOpen(); //先加入左界限符
.....//现在将对象的name名字添加到value后面
value += valueDelimClose(); //加入右界限符
return value;
}
CPerson和PersonInfo的关系是,PersonInfo刚好可以帮助实现CPerson,因此它们的关系就构成了is-implemented-in-terms-of(根据某物实现出),而我们知道这种关系可以两种技术实现,复合和private继承.前一条款指出复合比较受到欢迎,但如果需要重新定义virtual函数,那么继承是必要的.本例中CPerson需要重新定义valueDelimOpen与valueDelimClose,所以我们直接用private继承.
class CPerson:public IPerson,private PersonInfo{
public:
explicit CPerson(DatabaseID personID):PersonInfo(personID){}
virtual std::string name()const{
return PersonInfo::theName();
}
virtual std::string birthDate()const{
return PersonInfo::theBirthDate();
}
private:
const std::string valueDelimOpen()const{return "";}
const std::string valueDelimClose()const{return "";}
};
好了,这个设计还不错吧.呵呵,其实这个例子是告诉你,多重继承也有它的合理用途.
请记住:
■ 多重继承比单一继承复杂.它可能导致新的歧义性,以及对virtual继承的需要.
■ virtual继承会增加大小、速度、初始化(赋值)复杂度等到成本.如果virtual base classes不带任何数据,将是最具实用价值的情况.
■ 多重继承的确有正当用途.其中一个情节涉及"public继承某个Interface class"和"private继承某个协助实现的class"的两点相组合.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: