Effective C++(条款39-40)

来源:互联网 发布:知乎 大野智 编辑:程序博客网 时间:2024/05/29 03:55

条款39:明智而审慎的使用private继承

private继承意味着is-implemented-in-terms-of(根据某物实现出)。它通常比复合的级别低。但是当继承类需要访问基类的protected成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。


先了解一下private继承的特点:

1、如果类之间的继承关系是private,编译器不会将一个继承类对象转换为一个基类对象,这意味着private继承并不是is-a的关系。

class Person  {  protected:      string name;  };    class Student:private Person  {  private:      string schoolNumber;  };    void eat(const Person& p)  {      cout<<"eat"<<endl;  }    void study(const Student& s)  {      cout<<"study"<<endl;  }    int main()  {      Person p1;      eat(p1);      Student s1;      study(s1);  //  eat(s1);错误      return 0;  }  


2、由private base class继承而来的所有成员,在继承类中都会变成private属性。纵使他们在基类中原本是protected或public属性。


因此,private继承意味着:根据某物实现。这与前面条款介绍的复合很类似。private继承意味只有实现部分被继承,接口部分应该被略去。继承类根据基类实现而得,不存在其他的什么关系了。在大多数时候,我们应该使用复合,而不是private继承来实现这种功能。但是当有protected成员和虚函数牵扯进来的时候,我们又不得不用private继承。


其次,与复合相比,private可以使空基类的最优化。先看一个例子:

//定义一个空基类  class Empty{};    class HoldsAnInt  {  private:      int x;      Empty e;//复合  };  

在vs中,sizeof(int)为4,sizeof(Empty)为1,sizeof(HoldsAnInt)为8这似乎有悖于我们学过常理:首先,类的大小取决于其数据成员的大小。sizeof(Empty)应该为0,但是由于在编译器会将它的大小设为1,而“齐位需求”会将它放大为1个int,所以,izeof(HoldsAnInt)为8。但是,如果使用的是private继承来实现,就不存在这种问题了:sizeof(HoldsAnInt)只有一个int的大小:4。


 总之,private继承意味着根据某物实现。当派生类需要访问基类的的受保护成员或者重新定义虚函数时,我们才使用它。而且private继承可以是得空基类最优化,如果在开发中需要是得对象尺寸最小,那么也用得着它。“明智而审慎的使用private继承”意味着,在考虑过所有其他方案后,如果仍然认为private继承是“表现程序内两个类之间的关系”的最佳办法,这才使用它




条款40:明智而审慎的使用多重继承

1、多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要

当使用多重继承(MI)时,程序可能从一个以上base classes继承相同名称(函数、typedef等),这会导致歧义:

  clas BorrowableItem{    public:        void checkOut();        ……    };    class ElectronicGadgent{    private:        bool checkOut() const;        ……    };    class MP3Player: public BorrowableItem, public ElectronicGadget    {        ……    };    MP3Player mp;    mp.checkOut();//歧义,调用哪个checkOut

为了解决歧义,必须指明调用哪一个base class内的函数

mp.BorrowableItem::checkOut();

多重继承还可能会导致“钻石型多重继承”

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。


从正确行为的观点看,public继承应该总是virtual、如果这是唯一一个观点,那么任何时候当你使用public继承时,请改用virtual public继承。


2、但是,virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本,如果virtual base class不带任何数据,将是最具实用价值的情况

对virtual 继承的忠告:非必要不要使用virtual继承,平常请使用non-virtual继承;如果必须使用virtual继承,尽可能避免在其中放置数据。



3、多重继承的确有正当用途。其中一个情节设计“public继承某个类的接口”和“private继承某个协助实现的类”的组合

class IPerson{   //这个类指出需实现的接口    public:        virtual ~IPerson();        virtual std::string name() const=0;        virtual std::string birthDate() const=0;    };    class DatabaseID{……};//稍后被使用:细节不重要    class PersonInfo{      //这个类有若干有用函数,可以以实现IPerson接口    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              //实现必要的IPerson成员函数        {            return PersonInfo::theName();        }        virtual std::string birthDate()        {            return PersonInfo::theBirthDate();        }    private:        const char* valueDelimOpen() const{return "";}  //重新定义继承的virtual成员函数        const char* valueDelimClose() const{return "";}    };

多重继承只是面向对象的一个工具而已。和单一继承比较,它比较复杂,也难以理解,所以如果有一个单一继承方案和一个多重继承方案,那么单一继承方案比较受欢迎。但是如果通过多重继承可以完成任务,而且最简洁、最易维护、最合理,那么就不用怕使用它。



参考:http://blog.csdn.net/thefutureisour/article/details/7985263

            http://blog.csdn.net/KangRoger/article/details/44161773

            

0 0