zoukankan      html  css  js  c++  java
  • effective C++ 条款 40:明智而审慎地使用多重继承

    一旦涉及多重继承(multiple inheritance;MI):

    程序有可能从一个以上的base class继承相同名称(如函数、typedef等)。那会导致较多的歧义机会。例如:

    class BorrowableItem {
    public:
        void checkOut();
    };

    class ElectronicGadet {
    private:
        bool checkOut() const;
    };

    class MP3Player: public BorrowableItem
    public ElectronicGadet

    {...};
    MP3Player mp;
    mp.checkOut();//歧义,调用的是哪个checkOut?

    即使两个之中只有一个可取用(ElectronicGadet是private)。这与c++用来解析重载函数调用的规则相符:在看到是否有个函数可取之前,c++首先确认这个函数对此调用之言是最佳匹配。找出最佳匹配才检验其可取用性。本例的两个checkOut有相同的匹配程度。没有所谓最佳匹配。因此ElectronicGadget::checkOut的可取用性就从未被编译器审查。

    为了解决这个歧义,必须明白指出你要调用哪个base class内的函数:

    mp.BorrowableItem::checkOut();

    你当然也可以明确调用ElectronicGadget::checkOut(),但然后你会获得一个“尝试调用private成员函数”的错误。

    当即称一个以上的base classes,这些base classes并不常在继承体系中有更高级的base classes,因为那会导致要命的“钻石型多重继承”:

    class File{...};
    class InputFile: public File {...};
    class OutputFile: public File{...};
    class IOFile: public InputFile,
                        public OutputFile
    {...};

    任何时候只要你的继承体系中某个base class和某个derived class之间有一条以上的想通路线,你就必须面对这样一个问题:是否打算让base class内的成员经由每一条路径被复制?假设File有个成员变量fileName,那么IOFile应给有两份fileName成员变量。但从另一个角度来说,简单的逻辑告诉我们,IOFile对象只有一个文件名称,所以他继承自两个base class而来的fileName不能重复。

    c++的缺省做法是执行重复。如果那不是你要的,你必须令那个带有此数据的base class(也就是File)成为一个virtual base class。必须令所有直接继承自它的classes采用“virtual继承”:

    class File{...};
    class InputFile: virtual public File {...};
    class OutputFile: virtual public File{...};
    class IOFile: public InputFile,
                        public OutputFile
    {...};

    c++标准程序库内含一个多重继承体系,只不过其class是class template: basic_ios,basic_istream,basic_ostream和basic_iostream。

    从正确行为来看,public继承应该总是virtual。如果这是唯一一个观点,规则很简单:任何时候当你使用public继承,请改用virtual public继承。但是,正确性并不是唯一观点。为避免继承来的成员变量重复,编译器必须提供若干幕后戏法,其后果就是:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes成员变量速度慢

    virtual继承的成本还包括其他:支配“virtual base classes初始化”的规则比起non-virtual base的情况远为复杂和不直观。virtual base 的初始化责任是由继承体系中的最底层(most derived)class负责,1、class若派生自virtual base class而需要初始化,必须认知其virtual bases----不论那些bases距离多远,2、当一个新的derived class加入继承体系中,它必须承担起virtual bases(不论直接或间接)的初始化工作

    我们对virtual继承的忠告:第一,非必要不要使用virtual bases。第二,如果必须使用virtual bases,尽可能避免在其中放置数据。这样你就不需担心这些classes身上的初始化(和赋值)所带来的诡异事情了。

    下面看看这个C++Interface class:

    class IPerson{
    public:
        virtual ~IPerson();
        virtual std::string name() const = 0;
        virtual std::string birthDate() const =0;
    };

    //factory function,根据一个独一无二的数据库ID创建一个Person对象
    std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);
    DatabaseID askUserForDatabaseID();
    DatabaseID id(askUserForDatabaseID());
    std::tr1::shared_ptr<IPerson> pp(makePerson(id));

    假设一个派生自IPerson的具象class CPerson,它必须提供“继承自Iperson”的pure virtual函数的实现代码。我们可以写出这些,但更好的是利用既有组件。例如有个既有的数据库相关class,PersonInfo:

    class PersonInfo{
    public:
        explicit PersonInfo(DatabaseID pid);
        virtual ~PersonInfo();
        virtual const char* theName()const;
        virtual const char* theBirthDate() const;
    private:
        virtual const char* valueDelimOpen() const;
        virtual const char* valueDelimClose() const;
    };

    PersonInfo被设计用来协助以各种格式打印数据库字段,每个字段值的起始点和结束点以特殊字符串为界。默认为“[”,“]”

    ,但并非人人都爱方括号,所以提供两个virtual函数valueDelimOpen和ValueDelimClose语序derived class设定他们自己的头尾界限符号。PersonInfo成员函数将调用这些virtual函数,把适当的界限符号添加到它们的返回值上。PersonInfo::theName的代码看起来像这样:

    const char* PersonInfo::valueDelimOpen() const
    {
        return "[";//default
    }
    const char* PersonInfo::valueDelimClose() const
    {
        return "]";//default
    }
    const char* PersonInfo::theName() const
    {
        //保留缓冲区给返回值使用:static,自动初始化为“全0”
        static char value[Max_Formatted_Field_Value_Length];
        //写入起始符号
        std::strcpy(value, valueDelimOpen());
        将value内的字符串附到这个对象的name成员变量中
        //写入结尾符号
        std::strcat(value, valueDelimClose());
        return value;
    }

    所以theName返回的结果不仅仅取决于PersonInfo也取决于从PersonInfo派生下去的classes。

    Cperson和personInfo的关系是,PersonInfo刚好有若干函数可帮助Cperson比较容易实现出来。因此它们的关系是is-implemented-in-term-of。这种关系可以两种技术实现:复合和private继承。一般复合必要受欢迎,本例之中Cperson要重新定义valueDelimOpen和valueDelimClose,所以直接的解法是private继承。

    Cperson还有必须实现Iperson的接口,那得要public继承才能完成。这导致多重继承的一个通情达理的应用:将“public继承自某接口”和“private继承自某实现”结合在一起

    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() const
        {
            return PersonInfo::theBirthDate();
        }
    private:
        const char* valueDelimOpen() const {return "";}
        const char* valueDelimClose() const {return "";}
    };

    如果你唯一能提出的设计涉及多重继承,你应该再努力想一想----几乎可以说一定会有某些方案让单一继承行的通。然而有时候多继承的确是完成任务最简洁、最易维护、最合理的做法,就别害怕使用它。只是确定,的确在明智而审慎的情况下使用它。

  • 相关阅读:
    Cocos2d-x学习之---自定义图标(带触摸事件)
    Cocos2d-x关于ScrollView
    学习实战三:基于Cocos2d-x引擎模仿微信打飞机游戏
    补算法相关知识一:蚂蚁算法
    避免Cocos2d-x编写的游戏在用eclipse生成安卓包时繁琐的写Android.mk文件
    Cocos2d-x学习之---模仿微信打飞机游戏敌机层设计初想
    Cocos2d-x学习之---2013年10月11日小记
    有时候真怕,时间会说出真心话。
    NO2:设置RedHat Linux下的samba开机启动
    NO1:在Windows端安装SecureCRT来连接Linux
  • 原文地址:https://www.cnblogs.com/lidan/p/2351194.html
Copyright © 2011-2022 走看看