《Effective C++》学习笔记——条款40

六、继承与面向对象


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


两个阵营
一旦提到多重继承,C++社群便会分成两个基本阵营:

如果单一继承(SI - single inheritance)是好的,那么多重继承(MI - multiple inheritance)一定更好
单一继承是好的,但多重继承不值得使用
本条款的目的并不是讨论两者好坏,支持or反对哪个阵营,而是来了解这两个阵营。


两个观点
多重继承 可能导致歧义
尤其是当程序可能从多个基类继承相同名称(函数、typedef等)

class BorrowableItem {
public:
void checkOut();
…
};
class ElectronicGadget {
private:
bool checkOut() const;
…
};
class MP3Player: public BorrowableItem, public ElectronicGadget
{ … };
MP3Player mp;
mp.checkOut(); // 此处的checkOut是BorrowableItem类的还是ElectronicGadget类的呢?

注释①

为了解决这种歧义,首先要明确调用哪个基类的函数,然后 带上那个基类

mp.BorrowableItem::checkOut();


当然,此处也可以将 BorrowableItem换成ElectronicGadget,但是会报 无法调用private成员 的错误。

钻石型多重继承 问题
当所继承的基类在它们体系中又有共同的基类,会产生钻石型多重继承问题。

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

IOFile继承自InputFile与OutputFile类,但它们各自又有共同的基类File。
对于IOFile类中有多少 File类的成分呢?

IOFile应该从每个基类中获取一份成员函数,所以,它应该有两份
IOFile对象只该有一个文件名称,所以继承而来的函数不应该重复
C++ 对上述两种都支持,但缺省的方法是 执行复制,也就是有两份。
如果想让其是第二种方法,就需要将高级的基类(也就是File)设置为virtual base class

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


public继承 应该总是 virtual
理由:

使用virtual继承的那些类所产生的对象往往比使用non-virtual继承的兄弟们体积大
访问virtual base class的成员变量时,比访问non-virtual base class的成员变量速度慢
支配 virtual base class 初始化的规则比 non-virtual base情况复杂且不直观
忠告:

非必要,不适用 virtual base,平常使用non-virtual继承
如必须使用virtual base class,尽可能避免在内放置数据
多重继承 的 合理用途
一个关于人的接口类:

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



如果想使用IPerson,就必须用它的 指针 或者 引用,因为 抽象类无法被实体化创建对象,为了创建一些可以被使用的对象,就需要用工厂函数将 派生自IPerson的具象类实体化

std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier);
DatabaseID askUserForDatabaseID();
DatabaseID id(askUserForDatabaseID);
// 创建一个对象支持Iperson接口,通过成员函数处理*pp
std::tr1::shared_ptr<IPerson> pp(makePerson(id));
...


如果这个类名为CPerson,就像具象类一样,CPerson必须提供 继承自IPerson的 pure virtual函数的实现代码:

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被设计来协助打印各种格式的数据库字段,每个字段值的起点与终点都以特殊字符串为界。缺省的是方括号,比如

[Ring-tailed Lemur]


当然,可以更换界限符号,通过 valueDelimOpen 与 valueDelimClose

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];
    // 写入起始符号
    std::strcpy(value, valueDelimOpen());
    ...    // 将value内字符串添加到此对象的name成员变量中(注意,不要超出限制)
    // 写入结尾符号
    std::strcat(value, valueDelimClose());
    return value;
}


CPerson和PersonInfo的关系是,PersonInfo有若干函数帮助CPerson实现,所以是 is-implemented-in-terms-of。
本例中,CPerson需要重定义valueDelimOpen与valueDelimClose,所以 单纯的复合无法满足,如果非要用复合,就要用 复合+继承 的形式(令CPerson以private形式继承PersonInfo)

如果 使用private继承

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* theBirthDate() const;
    virtual const char* valueDelimOpen() const;
    virtual const char* valueDelimClose() const;
    ...
};

class CPerson: public IPerson, private PersonInfo {
public:
    explicit CPerson(DatabaseID pid): PersonInfo(pid) {}
    // 实现必要的IPerson成员函数
    virtual std::string name() const
    {  return PersonInfo::theName();  }
    virtual std::string birthDate() const
    {  return PersonInfo::theBirthDate();  }
private:
    // 重定义 继承而来的virtual界限函数
    const char* valueDelimOpen() const {  return "";  }
    const char* valueDelimClose() const {  return "";  }
};



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


注释
- ① C++解析重载函数调用的规则:在看到是否有函数可调用之前,C++首先确认这个函数对此调用是否是最佳匹配。找出最佳匹配才去检验科取用性。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值