码迷,mamicode.com
首页 > 编程语言 > 详细

《Effective C++》:条款38-条款39

时间:2015-03-08 21:35:00      阅读:229      评论:0      收藏:0      [点我收藏+]

标签:高效c++   private继承   c++   继承   

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

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

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和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<typename T>
class Set: public std::list<T>
{
    ……
};

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

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

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继承

public继承是is-a关系,**条款**32曾讲过并给出例子,如果把那个例子用private继承会怎样?

class Person{……};
class Student: private Person{……};
void eat(const Person& p);
void study(const Student& s);

Person p;
Student s;

eat(p);
eat(s);

上的的eat(s)会出错,因为private继承不是is-a关系。如果继承关系是private,那么编译器不会自动将一个derived class对象转换为base class对象;继承base的所有成员,在derived class中都是private。

private继承意味implemented-in-terms-of(根据某物实现出)。如果class D以private形式继承class B,我们的用意是采用class B内已经具备的某些特性。private继承纯粹只是一种实现技术(这也是为什么derived class中,base class成员都是private的:因为它们都只是实现枝节而已)。private继承意味只有实现部分被继承,接口部分应略去。D以private形式继承B,意思是D对象是根据B对象实现而得。

private继承意味is-implemented-terms-of(根据某物实现出),和**条款**38的复合意义相同。那么如何在两者之间取舍?答案是尽可能的复合,必要时才使用private继承。例如,当protected成员或virtual函数牵扯进来的时候。还有一种激进情况,当空间方面厉害关系足以踢翻private继承支柱时,这个稍后讨论。

现在有个Widget class,我们想记录每个成员函数调用次数,在运行期间周期性审查这份信息。为了完成这项工作,需要用到定时器:

class Timer{
public:
    explicit Timer(int tickFrequency);
    virtual void OnTick() const;//定时器滴答一次,此函数调用一次
    ……
};

上面是可以调整频率的订一起,每次滴答调用某个virtual函数,我们可以重新定义那个virtual函数,来取出Widget当时状态。为了重新定义Timer内的virtual函数,Widget必须继承Timer。因为Widget不是Timer,因此不适用public继承。还有一个观点支持不适用public,Widget对象调用onTick有点奇怪,会违反条款18:让接口容易被正确使用,不容易被误用。

class Widget: private Timer{
private:
    virtual void onTick() const;//查看Widget的数据等操作
    ……
};

这个设计也可以通过复合实现

class Widget{
private:
    class WidgetTimer: public Timer{
    public:
        virtual void onTick() const;
        ……
    };
    WidgetTimer timer;
    ……
};

这个设计稍微复杂一点,涉及到了public继承和复合,以及导入一个新class。我们有理由来选择这个复合版本,而不是private继承版本。

第一,Widget可能会有派生类,但是我们可能会想阻止在派生类中重新定义onTick。如果是使用private继承,上面的想法就不能实现,因为derived classes可以重新定义virtual函数(**条款**35)。如果采用复用方案,Widget的derived classes将无法采用WidgetTimer,自然也就无法继承或重新定义它的virtual函数了。这个像Java中的final或C#中的sealed。

第二,采用复合方案,还可以降低编译依存性。如果Widget继承Timer,当Widget编译时Timer的定义必须课件,所以Widget所在的定义文件必须包含Timer的定义文件。复合方案可以将WidgetTimer移出Widget,而只含有一个指针即可。

private继承主要用于“当一个意欲成为derived class者想访问一个意欲成为base class者的protected成分,或为了重新定义一个或多个virtual函数”。这时候,两个classes之间关系是is-implemented-in-terms-of,而不是is-a。有一种激进情况涉及空间最优化,会促使你选择private继承,而不是继承加复合。

这个情况真够激进,只适用于你所处理的class不带任何数据。它不包含non-static变量、virtual函数,没有继承virtual base class。这样的empty classes对象没使用任何空间,因为它没有任何数据对象要存储。但是因为技术原因,C++对象都必须有非零大小

class Empty{};
class HoldsAnInt{
private:
    int x;
    Empty e;
};

sizeof(HoldsAnInt)>sizeof(int)。大多数编译器中,sizeof(Empty)为1,通常C++官网勒令安插一个char到对象内,但class大小还有字节对其需求。

“独立(非附属)”对象大小一定不为零,这个约束不适用于derived class对象内的base成分,因为它们不独立,如果继承Empty,而不是复合

class HoldsAnInt: private Empty{
private:
    int x;
};

这时,几乎可以确定sizeof(HoldsAnInt)==sizeof(int)。这是所谓的EBO(empty base optimization;空白基类最优化)。如果客户非常在意空间,那么使用EBO。EBO一般只在单一继承下才行,统治C++对象布局的那些规则通常表示EBO无法被施行余“拥有多个base”的derived classes身上。

empty class并不是真的empty。它们内往往含有typedef、enum、static或弄-virtual函数。SLT有许多技术用途的empty classes,其中内含有的成员(通常是typedefs),包括base classes unary_function和binary_function,这些是“用户自定义之函数对象”,通常会继承的classes。

前面提到,只要可以尽可能选择复合,但这也不是全部。当面对并不存在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最优化。这对致力于“对象占用空间最小化”的程序库开发者而言,可能很重要。

《Effective C++》:条款38-条款39

标签:高效c++   private继承   c++   继承   

原文地址:http://blog.csdn.net/kangroger/article/details/44137943

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!