您的位置:首页 > 编程语言 > C语言/C++

Effictive c++条款38-40

2015-12-06 22:04 435 查看

条款38:通过复合塑模树has-a 或“根据某物实现出”

复合(composition)是类型之间的一种关系,一个类型的对象包含其他类型对象便是这种关系:

[code]class Address{ …… };
class PhoneNumber{ …… };
class Person{
public:
    ……
private:
    std::string name;
    Address address;
    PhoneNumber mobilePhone;
};


Person对象中包含string,Address,PhoneNumber对象,这就是复合。还有几个同义词:layering(分层),containment(内含),aggregation(聚合),embedding(内嵌)。

条款 32中提到,public是is-a关系,复合是has-a(有一个)或is-implemented-in-terms-of(根据某物实现出)。在程序中,大概可以分为两个领域(domains)。程序中对象相当于你所塑造现实世界中某物,例如地址、电话号码,这样的对象属于应用域(application domain)。还有一些是实现细节上的人工复制品,例如缓冲区(buffers)、互斥器(mutexes)、查找树(search tree)等,这些是实现域(implementation domain)。当复合发生在应用域对象之间时,表现出has-a关系;发生在实现域表现出is-implemented-in-terms-of关系。

根据字面意思理解即可区分is-a和has-a。人有一个地址而不是人是一个地址。

区分is-a和is-implemented-in-terms-of比较麻烦。通过一个例子来说明,假设你需要一个template,用来构造一组classes来表示不重复对象组成的sets。首先我们想到用标准程序库提供的set template。

标准程序库的set由平衡查找树(balance search tree)实现,每个元素使用了三个指针的额外开销。这样可以使查找、插入、移除等操作时间复杂度为O(logN)(对数时间,logarithmic-time)。如果速度比空间重要,这样做合理,但是如果空间比速度重要,那么标准库提供的set将不满足我们需求。

set实现方法很多,可以在底层使用linked lists来实现,标准库中有list template,于是我们复用它。

template

class Set: public std::list

{

……

};

上面看起来很美好,其实是错误的。条款 32曾说过,public继承是is-a关系,即set是一种list并不对。例如set不能包含重复元素,但是list可以。

因为这两个classes之间并非is-a关系,所以public继承并不适用。set对象可以根据一个list对象来实现出来:

[code]template<calss T>
class Set{
public:
    bool member(const T& item) const;
    void insert(const T& item);
    void remove(const T& item);
    std::size_t size() const;
private:
    std::list<T> rep;
};


只要熟悉list,便很快可以实现上面几个接口函数。

条款 18主张,接口容易被正确适用,不易被误用。这里没有让set遵循STL容器的协议,如果遵循的话,要为set添加许多东西,这就模糊了set和list之间的关系。这里只是澄清set和list之间的关系。

请记住:

◆ 复合(composition)的意义和public继承完全不同。

◆ 在应用域(application domain),复合意味has-a;在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。

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

private 继承:

如果classes之间的继承关系是private ,编译器不会自动将一个derived class 对象转化成一个base class对象。

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

复合和private继承都意味着 is-implemented-in-terms-of,但是复合比较容易理解

前面提到,只要可以尽可能选择复合,但这也不是全部。当面对并不存在is-a关系的两个classes,其中一个需要访问另一个的protected成员,或需要重新定义其一个或多个virtual函数,private继承可能成为正统设计策略。在考虑了其他方案后,仍然认为private继承是“表现两个classes之间的关系”的最佳办法,那就使用它。

请记住:

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

◆ 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象占用空间最小化”的程序库开发者而言,可能很重要。

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

对于多重继承(multiple inheritance:MI),在C++阵营中有不同主张。一个是:如果认为单一继承(single inheritance:SI)是好的,那么多重继承一定也是好的;另一个是:单一继承是好的,但是多重继承不是。本条款主要让大家了解这两个观点。

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

[code] clas BorrowableItem{
    public:
        void checkOut();
        ……
    };
    class ElectronicGadgent{
    private:
        bool checkOut() const;
        ……
    };

    class MP3Player: public BorrowableItem, public ElectronicGadget
    {
        ……
    };
    MP3Player mp;
    mp.checkOut();//歧义,调用哪个checkOut


注意,虽然上面两个函数一个是public,一个是private,但还是有歧义。这与C++用来解析(resolving)重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用是最佳匹配,在找出最佳匹配函数后才检验其可取性。上面两个checkOut有相同匹配度。为了解决歧义,必须指明调用哪一个base class内的函数

[code]mp.BorrowableItem::checkOut();


多重继承是继承一个以上的base classes,但是这些base class常在继承体系中又有更高级的base classes ,因为那会导致要命的“砖石型多重继承”,即从基类到某个子类之间有一条以上的相通路线,这样就会有歧义。解决方案:采用virtual base class

使用virtual继承的对象比non-virtual继承的对象体积大,访问virtual base classes成员变量时,也比访问non-virtual base classes成员变量速度慢。可以参考这里

virtual继承成本还包括virtual base classes初始化。virtual base的初始化有继承体系中最底层(most derived)class负责。这表示(1)classes派生自virtual bases需要初始化,必须认知其virtual bases;(2)当一个新的derived class加入继承体系中,它必须承担virtual bases(直接或间接)的初始化责任。

对virtual继承的忠告是:(1)非必要时,不使用virtual继承,平时使用non-virtual继承。(2)如果必须使用virtual继承,那么尽量避免在base内放置数据,这样就不用担心在这些classes身上的初始化和赋值带来的诡异事情了。Java和.NET的Interfaces在许多方面兼容C++的virtual base classes,它不允许带有任何数据。

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

请记住:

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

◆ virtual继承会在对象大小、速度、初始化、赋值造成成本增加。如果virtual base class不带任何数据,那么将是最具有使用价值情况。

◆ 多重继承的确有正当用途。其中一个情节设计public继承某个Interface class和private继承某个协助实现的class。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: