条款40、明智地使用多重继承

本文探讨了多重继承带来的歧义及其解决方案——虚继承,并通过示例展示了如何使用虚继承来避免成员变量的重复。此外,还介绍了虚继承可能带来的额外开销。
	现在考虑多重继承MI(multiple inheritance)。程序可能从一个以上的基类继承相同的名称(如函数 typedef等)。那会导致较多歧义。考虑以下代码:
class  A
{
         public:
         void fun();
….
};
class B
{
         private:
         bool fun()const;
};
class C:public A,public B   //多重继承
{…};
C c;
c.fun();       //调用的是哪个类的fun()?

此例的函数调用有歧义的,即使两个函数只有一个能调用(A的为公有,而B却是私有的。)
。我们用C+重载函数的调用规则找出最佳匹配,但本例中的两函数却有相同的匹配度。所以B::fun()的可取用性也没被编译器审查。
为了解决这个歧义,我们必须明确调用,如  c.A::fun();当然也能c.B::fun(),但会出现尝试调用私有成员的错误。

MI继承一个以上的基类,而这些在继承体系中又有更高的基类,这会导致要命的”钻石多重继承”.
class File{…};
class InputFile:public File{…};
class OutFile:public File{…};
class IOFile:public InputFile,public OutFile{…};


如果任何基类和某派生类之间有一条以上的路线,这就要面对一个问题:基类成员是否应该经由每一条路线复制。如上,如果File中有一成员fileName,IOFile从每个基类继承就应该有两份该变量,但简单逻辑告诉我们,IOFile 对象应该只有一个名称,继承基类而来的成员不能重复。
C+的立场:两方法都支持。---缺省方法是执行复制。如果不想这么做,就应该让带有此数据的类成为虚基类。所有直接继承它的类采用虚继承:
class File{…};
class InputFile:virtual public File{…};
class OutFile:virtual public File{…};
class IOFile:public InputFile,public OutFile{…};


C+标准程序库内含一个多重继承体系,结构就如上图,只不过类都是类模版。分别是basic_ios,basic_istream,basic_ostream,basic_iostream,
从正确行为来看:公有继承应该总是virtual。
但正确性不是唯一 观点,为避免继承成员重复,编译器必须提供若干幕后工作,造成的后果是:1、虚继承的类产生的对象往往比非虚继承类产生的对象大。2、访问虚基类成员变量也相对慢。虽然细节和编译器有关,但结论是,得为虚继承付出代价
虚继承的成本还包括其他方面(虚基类初始化):1、类若派生自虚基类而要初始化,就必认知其虚基类---无论基类有多远 。2、当新的派生类加入继承体系,必须承担虚基类(无论直或间接)的初始化责任
使用忠告:1、如非必要不使用虚基类    2、如果必须使用虚基类,必免在其中放置数据。
考虑以下接口类:
class IPerson
{
         public:
         virtual~IPerson();
         virtrualstd::string birth() const=0;
         virtualst::string name() const=0;
};

因为抽象类,IPerson类必须以指针和引用来编写程序。我们使用工厂函数将“派生自IPerson的具象类”实体化。
//工厂函数,根据唯一数据库ID合建一个Person对象
//条款18解释为何返回类型不是原始指针 DBid  (Datebase id)
std::str::shared_ptr<IPerson>  makePerson(DBid  pid);
//此函数从使用者手上取得id
DBid   askDBid();
DBid id(askDBid());
std::str::shared_ptr<IPerson>  pp(makePerson(id));

makePerson创建对象并返回指针,无疑有某些派生自IPerson的具象类。在makePerson中创建对象。
假设这个类为CPerson。它必须提供“继承自IPerson”的纯虚函数的实现代码。又假设有一个和数据库相关的类PersonInfo,提供CPerson所需要的东西。
class PerosnInfo
{
         public:
                   explicitPersonInfo(DBid pid);
                   virtual~PersonInfo();
                   virtual const  char*  theName() const;
                   virtual constchar*  theBirth() const;
…
private:
virtualconst char* valueDeliOpen() const;   //详下
virtualconst char* valueDeliClose() const;   //虽然返回char*而非string  只要合适也是可以的
};

valueDeliOpen()函数用于格式化数据库字段时输出界定符。如A-B  C被格式化为[A-B C]
但因为不是每个人都喜欢默认的”[ ]”界定符,所以定义为虚函数---允许派生类重定义。
const char* PerosnInfo ::valueDeliOpen() const
{
         return “[”;           //缺省起始符号
}
const char* PerosnInfo ::valueDeliClose() const
{
         return “]”;           //缺省结束符号
}
const  char*   PerosnInfo ::theName() const
{
//保留缓冲区给返回值使用,因为是static,默认初始化全部为0
         static charval[MAX_LEN]; 
         std::strcpy(val,valueDeliOpen());
…..//将val字符串添加到name变量中
std::strcpy(val, valueDeliClose());  //写入结尾符
return val;
}


比如一个人名为abc,theName()函数返回值为[abc],也可能是“abc”.因为valueDeliOpen()是虚函数,所以theName的返回值不仅取决于PersonInfo也取决于PersonInfo派生下去的类。
CPerson和PerosnInfo的关系明显是根据某物实现出。TK39说明复合是比较好的方法。但如果我们要重新定义虚函数,继承是必须的。如本例中Cperson重新定义valueDeliOpen()等函数。单纯的复合无法满足要求。最直接的方法是CPerson私有继承自PersonInfo。但同时CPerson也必须实现IPerson的接口。故最后得出的方法是:
结合 公有继承接口私有继承自某实现
class IPerson
{
         public:
         virtual~IPerson();
         virtrualstd::string birth() const=0;
         virtualst::string name() const=0;
};
class DBid{….};  //后续使用,不在意细节
class PerosnInfo
{
         public:
                   explicitPersonInfo(DBid pid);
                   virtual~PersonInfo();
                   virtualconst  char*   theName() const;
                   virtualconst char*  theBirth() const;
virtualconst char* valueDeliOpen() const;   
virtualconst char* valueDeliClose() const;   //虽然返回char*而非string  只要合适//也是可以的
};
class CPerson:public IPerson,private PersonInfo   //多重继承
{
         public:
         explicit CPerson(DBidpid):Person(pid);  //实现必要的IPerson成员函数
         virtualstd::string name()
{reurnPersonInfo::theName();}
virtual std::string birth()
{return PersonInfo::theBirth();}
virtual const char* valueDeliOpen()const  {return “ ”;}  //重新定义继承而来的虚函数
virtual const char* valueDeliClose()const  {return “ ”;}
};

UML图如下:

这个例子说明多重继承也有它的合理用途。

需要记住的:
	1、多重继承比单一继承复杂,可能导致新的歧义及对虚继承的需要
	2、虚继承会增加大小、速度及初始化复杂度等成本。当虚基类不带任何数据才是最具实用价值的情况。
	3、在涉及公有继承接口,私有继承某协助实现的类时,多重继承很实用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值