首页 > 代码库 > Effective C++:条款40:明智而审慎地使用多重继承

Effective C++:条款40:明智而审慎地使用多重继承

(一)

 慎用多重继承,因为那样的话可能会造成歧义。。

<pre name="code" class="cpp">class BorrowableItem { 
public: 
    void checkOut(); 
};

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

class MP3Player : public BorrowableItem  
<pre name="code" class="cpp" style="font-size: 16px; line-height: 28px;">                  public ElectronicGadet
{...}; MP3Player mp; mp.checkOut();//歧义,调用的是哪个checkOut?

这与C++用来解析重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用之言是最佳匹配。找到最佳匹配函数后才检验其可取用性。本例中连最佳匹配都出现歧义,所以是否可取用就免谈了。
所以解决办法:

mp.BorrowableItem::checkOut();

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

(二)

如果base classes在继承体系中又有更高级的base classes,非virtual继承会造成多份base class成员变量。这时要用virtual继承。为了这样做,你必须令所有直接继承自它的classes采用“virtual继承”

下面这种缺省行为会导致类IOFile的对象中有两份File的副本:

class File{...}; 
class InputFile: public File {...}; 
class OutputFile: public File{...}; 
class IOFile: public InputFile, 
                    public OutputFile 
{...}; 
下面这种虚继承的行为会导致类IOFile的对象中只有一份File的副本:

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

(1)使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes成员变量速度慢。

(2)class若派生自virtual base class而需要初始化,必须认知其virtual bases----不论那些bases距离多远。

(3)当一个新的derived class加入继承体系中,它必须承担起virtual bases(不论直接或间接)的初始化工作

(4)我们对virtual继承的忠告:第一,非必要不要使用virtual bases;

                                      第二,如果必须使用virtual bases,尽可能避免在其中放置数据


(三)

多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”(接口)和“private继承某个协助实现的class”(实现)的两相结合。例子:

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

class DatabaseID { ... };    //稍后被使用

class PersonInfo {           //这个class有若干有用函数,可用以实现IPerson接口
    explicit PersonInfo(DatabaseID pid);
    virtual ~PersonInfo();
    virtual const char* theName() const;
    virtual const char* theBirthDate() const;
    virtual const char* valueDelimOpen() const;
    virtual const char* valueDelimClose() const;
};

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

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() const {     //实现必要的IPerson成员函数	return PersonInfo::theBirthDate();
    }
private:
    const char* valueDelimOpen() const {       //重新定义继承而来的virtual“界限函数”
		return "";
    }
    const char* valueDelimClose() const {
        return "";
    }
};

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

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

(3)  多重继承&单一继承比较,它通常比较复杂,使用上也比较难以理解,所以如果你有个单继承的设计方案,而它大约等价于一个多重继承设计方案,那么单一继承设计方案几乎一定比较受欢迎。

(4)  如果你唯一能够提出的设计方案涉及多重继承,你应该更努力想一想:几乎可以说一定会有某些方案让单一继承行得同。

(5)  然而多继承有时候的确是完成任务之最简洁、最易维护、最合理得做法,果真如此就别害怕使用它。


请记住:

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

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

(3)多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。