《Effective C++》:条款40:明智而审慎的使用多重继承
2015-03-09 22:20
232 查看
多重继承(multiple inheritance:MI)在C++阵营中有不同主张。一个是:如果认为单一继承(single inheritance:SI)是好的,那么多重继承一定也是好的;另一个是:单一继承是好的,但是多重继承不是。本条款主要让大家了解这两个观点。
当使用MI时,程序可能从一个以上base classes继承相同名称(函数、typedef等),这会导致歧义(ambiguity)
注意,虽然上面两个函数一个是public,一个是private,但还是有歧义。这与C++用来解析(resolving)重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用是最佳匹配。上面两个
当然可以调用ElectronicGadget::checkOut,但你会得到一个“尝试调用private成员函数”错误。
多重继承还可能会导致“菱形继承”
在上面这个继承体系中,IOFile会有File的两重拷贝,因为InputFile和OutFile都有一份拷贝,而IOFile有这两个class的拷贝。假如File class有个成员变量fileName,那么IOFile内有两个这样的数据。IOFile继承base classes,所以应该有两份,但是这两个变量是重复的,fileName只需一份即可。
C++在是否需要2份拷贝的辩论中没有立场,两个方案它都支持,缺省方案是第一个方案。如果要用第二个方案,那么就要让带此数据的class(即File)成为一个virtual base class。
C++标准库有一个多重继承体系,是个class templates。名字分别是basic_ios,basic_istream,basic_ostream和basic_iostream。
从上面例子看,好像public继承都应该是virtual。但是这个观点不正确。为了避免成员变量重复,编译器付出了代价,使用virtual继承的对象比non-virtual继承的对象体积大,访问virtual base classes成员变量是,也比访问non-virtual base classes成员变量速度慢。可以参考这里
virtual继承成本还包括virtual base classes初始化。virtual base的初始化有继承体系中最底层(most derived)class负责。这表示(1)classes派生自virtual bases需要初始化,必须认知其virtual bases;(2)当一个新的derived class加入继承体系中,它必须承担virtual bases(直接或间接)的初始化责任。
对virtual继承的忠告是:(1)非必要时,不使用virtual继承,平时使用non-virtual继承。(2)如果必须使用virtual继承,那么尽量避免在base内放置数据,这样就不用担心在这些classes身上的初始化和赋值带来的诡异事情了。Java和.NET的Interfaces在许多方面兼容C++的virtual base classes,它不允许带有任何数据。
现在看一下**条款**31中用来塑造人的Interface class
客户使用IPerson的pointer或reference来编程,因为抽象类无法实例化。在创建对象是,IPerson的客户可以使用factory function(工厂函数)来将派生自IPerson的classes实例化。
假设makePerson创建的具体class为CPerson,那么CPerson必须提供继承自IPerson的pure virtual函数实现代码。假设现在还有与数据库相关的class PersonInfo,提供CPerson所需的一些东西
对于我们来说这是个好消息,我们可以使用PersonInfo的一些接口来完成相关设计,但是有两个函数不满足要求,
这样可以得到CPerson和PersonInfo的关系是PersonInfo刚好有若干函数可以帮助CPerson比较容易来实现。它们关系是is-implemented-in-terms-of(根据某物实现出),这种关系可以由两种技术实现:复合和private继承。**条款**39提到复合是比较受欢迎的,但如果要重新定义virtual 函数,那么必须继承。本例中,要重新定义函数,所以无法使用复合。
CPerson也要实现IPerson接口,所以要public继承它。这导致了多重继承
这个例子告诉我们多重继承也有它合理用途。
多重继承只是面向对象的一个工具而已。和单一继承比较,它比较复杂,也难以理解,所以如果有一个单一继承方案和一个多重继承方案,那么单一继承方案比较受欢迎。但是如果通过多重继承可以完成任务,而且最简洁、最易维护、最合理,那么就不用怕使用它。
总结
多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
virtual继承会在对象大小、速度、初始化、赋值造成成本增加。如果virtual base class不带任何数据,那么将是最具有使用价值情况。
多重继承的确有正当用途。其中一个情节设计public继承某个Interface class和private继承某个协助实现的class。
当使用MI时,程序可能从一个以上base classes继承相同名称(函数、typedef等),这会导致歧义(ambiguity)
clas BorrowableItem{ public: void checkOut(); …… }; class ElectronicGadgent{ private: bool checkOut() const; …… }; class MP3Player: public BorrowableItem, public ElectronicGadget { …… }; MP3Player mp; mp.checkOut();//歧义,调用哪个checkOut
注意,虽然上面两个函数一个是public,一个是private,但还是有歧义。这与C++用来解析(resolving)重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用是最佳匹配。上面两个
checkOut有相同匹配度。为了解决歧义,必须指明调用哪一个base class内的函数
mp.BorrowableItem::checkOut();
当然可以调用ElectronicGadget::checkOut,但你会得到一个“尝试调用private成员函数”错误。
多重继承还可能会导致“菱形继承”
class File{……}; class InputFile: public File{……}; class OutputFile: public File{……}; class IOFile:public InputFile, public OutputFile {……};
在上面这个继承体系中,IOFile会有File的两重拷贝,因为InputFile和OutFile都有一份拷贝,而IOFile有这两个class的拷贝。假如File class有个成员变量fileName,那么IOFile内有两个这样的数据。IOFile继承base classes,所以应该有两份,但是这两个变量是重复的,fileName只需一份即可。
C++在是否需要2份拷贝的辩论中没有立场,两个方案它都支持,缺省方案是第一个方案。如果要用第二个方案,那么就要让带此数据的class(即File)成为一个virtual base class。
class File{……}; class InputFile: virtual public File{……}; class OutputFile:virtual public File{……}; class IOFile:public InputFile, public OutputFile {……};
C++标准库有一个多重继承体系,是个class templates。名字分别是basic_ios,basic_istream,basic_ostream和basic_iostream。
从上面例子看,好像public继承都应该是virtual。但是这个观点不正确。为了避免成员变量重复,编译器付出了代价,使用virtual继承的对象比non-virtual继承的对象体积大,访问virtual base classes成员变量是,也比访问non-virtual base classes成员变量速度慢。可以参考这里
virtual继承成本还包括virtual base classes初始化。virtual base的初始化有继承体系中最底层(most derived)class负责。这表示(1)classes派生自virtual bases需要初始化,必须认知其virtual bases;(2)当一个新的derived class加入继承体系中,它必须承担virtual bases(直接或间接)的初始化责任。
对virtual继承的忠告是:(1)非必要时,不使用virtual继承,平时使用non-virtual继承。(2)如果必须使用virtual继承,那么尽量避免在base内放置数据,这样就不用担心在这些classes身上的初始化和赋值带来的诡异事情了。Java和.NET的Interfaces在许多方面兼容C++的virtual base classes,它不允许带有任何数据。
现在看一下**条款**31中用来塑造人的Interface class
class IPerson{ public: virtual ~IPerson(); virtual std::string name() const=0; virtual std::string birthDate() const=0; };
客户使用IPerson的pointer或reference来编程,因为抽象类无法实例化。在创建对象是,IPerson的客户可以使用factory function(工厂函数)来将派生自IPerson的classes实例化。
//根据ID创建一个Person对象 std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier); //从使用者手上取得一个数据库的ID DatabaseID askUserForDatabaseID(); DatabaseID id(askUserForDatabaseID()); std::tr1::shared_ptr<IPerson> pp(makePerson(id));
假设makePerson创建的具体class为CPerson,那么CPerson必须提供继承自IPerson的pure virtual函数实现代码。假设现在还有与数据库相关的class PersonInfo,提供CPerson所需的一些东西
class PersonInfo{ public: explicit PersonInfo(DatabaseID pid); virtual ~PersonInfo(); virtual const char* theName() const; virtual const char* theBirthdayDate() const; …… private: virtual const char* valueDelimOpen() const; virtual const char* valueDelimClose() const; …… };
对于我们来说这是个好消息,我们可以使用PersonInfo的一些接口来完成相关设计,但是有两个函数不满足要求,
valueDelimOpen()和
valueDelimClose(),这两个函数分别返回”[“和”]”,影响了其他函数,要重新定义。
这样可以得到CPerson和PersonInfo的关系是PersonInfo刚好有若干函数可以帮助CPerson比较容易来实现。它们关系是is-implemented-in-terms-of(根据某物实现出),这种关系可以由两种技术实现:复合和private继承。**条款**39提到复合是比较受欢迎的,但如果要重新定义virtual 函数,那么必须继承。本例中,要重新定义函数,所以无法使用复合。
CPerson也要实现IPerson接口,所以要public继承它。这导致了多重继承
class IPerson{ public: virtual ~IPerson(); virtual std::string name() const=0; virtual std::string birthDate() const=0; };
class DatabaseID{……};
class PersonInfo{ public: explicit PersonInfo(DatabaseID pid); virtual ~PersonInfo(); virtual const char* theName() const; virtual const char* theBirthdayDate() const; …… private: virtual const char* valueDelimOpen() const; virtual const char* valueDelimClose() const; …… };
class CPerson: public IPerson, private PersonInfo{
public:
explicit CPerson(DatabaseID pid): PersonInfo(pid){}
virtual std::string name() const
{
return PersonInfo::theName();
}
virtual std::string birthDate()
{
return PersonInfo::theBirthDate();
}
private:
const char* valueDelimOpen() const{return "";}
const char* valueDelimClose() const{return "";}
};
这个例子告诉我们多重继承也有它合理用途。
多重继承只是面向对象的一个工具而已。和单一继承比较,它比较复杂,也难以理解,所以如果有一个单一继承方案和一个多重继承方案,那么单一继承方案比较受欢迎。但是如果通过多重继承可以完成任务,而且最简洁、最易维护、最合理,那么就不用怕使用它。
总结
多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
virtual继承会在对象大小、速度、初始化、赋值造成成本增加。如果virtual base class不带任何数据,那么将是最具有使用价值情况。
多重继承的确有正当用途。其中一个情节设计public继承某个Interface class和private继承某个协助实现的class。
相关文章推荐
- effective C++ 条款 40:明智而审慎地使用多重继承
- 读书笔记《Effective C++》条款40:明智而审慎地使用多重继承
- Effective C++:条款40:明智而审慎地使用多重继承
- effective C++ 条款 39:明智而审慎地使用private继承
- 《Effective C++》读书笔记之item40:明智而审慎地使用多重继承
- Effective C++ 条款39 明智而审慎地使用private继承
- 《Effective C++》 Rule 40:明智而审慎地使用多重继承
- 条款40:明智而审慎地使用多重继承(use multiple inheritance judiciously)
- Effective C++:条款39:明智而审慎地使用private继承
- Effective C++ 条款40 明确而审慎地使用多重继承
- 条款40:明智而审慎地使用多重继承
- 条款40:明智而审慎地使用多重继承
- 条款40:明智而审慎的使用多重继承
- Effective C++:条款39:明智而审慎地使用private继承
- Effective C++ -----条款40:明智而审慎地使用多重继承
- 【Effection C++】读书笔记 条款40:明智而审慎的使用多重继承
- C++之明智而审慎地使用多重继承(40)---《Effective C++》
- Effective C++ Item 40 明智而审慎地使用多重继承
- 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承
- 【40】明智而审慎地使用多重继承