C++之明智而审慎地使用多重继承(40)---《Effective C++》

条款40:明智而审慎地使用多重继承(multiple inheritance:MI)

在讲多重继承之前,我们先来看看一个例子,探索一下多重继承中可能存在什么问题:

class BorrowableItem{
public:
    void checkOut();
    ...
};
class ElectronicGadget{
private:
    bool checkOut() const;
    ...
};
class MP3Player:public BorrowableItem,public ElectronicGadget{
    ...
}

MP3Player mp;
mp.checkOut();//调用的到底是哪个呢???

上面代码中,我们调用checkOut函数,由于多重继承,两个父类中都存在checkOut函数,即使两个函数中只有一个可以调用,但是并不影响其调用出现歧义,这与C++用来解析重载函数的规则相符合:在看到是否有个函数可调用之前,C++首先确认这个函数对此调用之言是最佳匹配,找到最佳匹配函数之后采检验其可取用性。本里的两个checkOut有着相同的匹配程度,没有最佳匹配,因此ElectronicGadget::checkOut的可取用性也就从未被编译器审查。
怎么解决呢?

mp.BorrowableItem::checkOut();//明确指定调用的是哪个checkOut,当然ElectronicGadget中的checkOut无法调用,因为尝试调用private成员函数。

多重继承中的另一个问题是“钻石型多重继承”,因为base classes在继承体系中可能存在一个共同的base class,这样就会特别复杂,结构见下图:
这里写图片描述
任何时候我们在继承体系中的某个base class和某个derived class之间有一跳以上的相通路线,这样就面临一个问题,base class的成员变量是否沿着每一条路径都被复制,从逻辑角度进行考虑的话我们不应该将其沿着每条路径都复制,因为这样导致最低下(most derived class)类中将有多个从base class中继承的成员变量,显然这会致使资源浪费,但是很不幸,C++的缺省默认做法是执行复制,也就是沿着每条路径对成员变量进行复制。,为了解决这个问题,我们需要是带有数据的最底层(base class)类成为一个virtual base class,为了这样做,我们需要采用“virtual 继承”:

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

既然virtual继承这么好,为什么我们不把所有的继承都声明为virtual,任何事物都有对立面,因此,我们不仅需要了解它的优点,同时更应该知晓它的不足。

C++中多重继承(Multiple Inheritance)的问题:

1. 子类继承的多个父类中存在相同函数,调用时容易引发歧义;

2. 子类继承的多个类有一个共同的父类,默认子类必须从每一条路径复制数据,造成数据冗余,采用virtual继承加以避免。

  • virtual继承的不足

virtual继承由于采用动态绑定,为了避免继承得来的成员变量重复,编译器必须提供若干幕后动作,其直接结果是:使用virtual集成的那些classes所产生的对象往往比使用non-virtual集成的兄弟体积更大,访问virtual base classes的成员变量时候,也比访问non-virtual base classes成员变量速度慢,种种细节因编译器不同而异,但基本重点很清晰:你需要为virtual继承付出代价。
virtual集成的成本还包括其他方面。支配“virtual base classes”初始化的规则比起non-virtual base的情况更为复杂和不直观。virtual base的初始化责任是由继承体系中的最低层(most derived)class负责,这暗示:1)classes若派生自virtual bases而需要初始化,必须认知其virtual base classes,不管那些base classes相距多远;2)当一个新的derived class加入继承体系中,他必须承担其virtual bases(不管直接或者间接)的初始化责任。

建议:
1)非必要不使用virtual bases,平时请使用non-virtual继承;
2)如果你必须使用virtual base classes,尽可能避免在其中放置数据,这样你就不低担心这些classes身上的初始化(和赋值)所带来的诡异事情了。

示例:
这个例子中,CPerson是通过PersonInfo类is-implemented-in-terms-of出来的,因为其中需要virtual函数重载,所以我们private继承了IPerson类,同时继public继承了IPerson类。

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

总结:
1)多重继承比单一继承复杂,可能导致新的歧义性,以及对virtual继承的需要;
2)virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况;
3)多重继承的确有正当用途,其中一个情节涉及“public继承某个Interface class”和“priavate继承某个协助实现的class”的两相组合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值