条款35 考虑virtual函数以外的其他选择

记住:

★virtual函数的替代方案包括NVI手法Strategy模式的多种形式。NVI手法自身是一个特殊形式的Template Method模式

★将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员

tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物

------------------------------------------------------------------------------------------

背景:

class GameCharacter {

public:

virtual int healthValue() const; //返回角色的健康指数

...

};

这是常规设计,以下探讨其替代解决方案。

借由non-virtual interface手法实现template method模式:

  NVI手法对public virtual函数而言是一个有趣的替代方案。令客户通过public non-virtual成员函数间接调用private virtual函数,称为NVI手法。它是所谓的template method模式的一个独特表现形式(注意此处应将其与看过的设计模式知识联系起来!!!)。此public non-virtual函数称为virtual函数的外覆器(wrapper

class GameCharacter {
public:
int healthValue() const {       //此即public non-virtual member 函数
... //做一些事前工作
int retVal = doHealthValue(); //做真正的工作
... //做一些事后的工作
return retVal;
}
...
private:
virtual int doHealthValue() const { //派生类可以重新定义它!!!
...
}
};

注意点:

a NVI手法一个优点隐身在上述代码注释“做一些事前、事后工作”之中。事前工作可包括锁定互斥器、制造运转日志记录项、验证class约束条件、验证函数先决条件等;事后工作包括互斥器解锁、验证函数的事后条件、再次验证class约束条件等等。

b 注意一个C++规则:derived classes可重写继承而来的private virtual函数!!!!!!!

----------------

借由函数指针实现策略模式:(注意联系到essential C++笔记中的通过面向过程的方法来实现多态性的数列类例子!!!)

class GameCharacter;

//以下函数是计算健康指数的缺省算法
int defaultHealthCalc( const GameCharacter &gc ); class GameCharacter { //类比于strategy模式的context类
public:
typedef int (*HealthCalcFunc)( const GameCharacter & ); explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
: healthFunc( hcf ) { } int healthValue() const {
return healthFunc( *this );
} private:
HealthCalcFunc healthFunc; //类比于context中的策略基类!!!
};

此做法是常见的策略模式的简单应用。拿它和“基于GameCharacter继承体系内之virtual函数”的做法比较,提供了以下弹性:

♢ 同一人物类型之不同实体可以有不同的健康计算函数,如:

class EvilBadGuy : public GameCharacter {
public:
explicit EvilBadGuy( HealthCalcFunc hcf = defaultHealthCalc )
: GameCharacter( hcf ) { }
...
}; int loseHealthQuickly( const GameCharacter & ); //健康指数计算函数1
int loseHealthSlowly( const GameCharacter & ); //健康指数计算函数2 EvilBadGuy ebg1( loseHealthQuickly ); //相同类型的人物搭配不同的健康计算方式
EvilBadGuy ebg2( loseHealthSlowly );

♢ 某已知人物之健康指数计算函数可在运行期变更。如GameCharacter可提供一个成员函数setHealthCalculator用来替换当前的健康指数计算函数。

-------------------------------

借由tr1::function实现策略模式:

即对上面方法的改进:此处不使用function pointer,而是改用一个类型为tr1::function的对象,这样的对象可持有任何可调用物(包括函数指针、仿函数、成员函数指针),只要其签名式兼容于需求端:

class GameCharacter;   //如前

//以下函数是计算健康指数的缺省算法
int defaultHealthCalc( const GameCharacter &gc ); //如前 class GameCharacter {
public:
//HealthCalcFunc可以是任何可调用物,可被调用并接收任何兼容于GameCharacter
//之物,返回任何兼容于int的东西
typedef std::tr1::function<int (const GameCharacter &)> HealthCalcFunc; //注意语法形式!!!
explicit GameCharacter( HealthCalcFunc hcf = defaultHealthCalc )
: healthFunc( hcf ) { }   int healthValue() const {
return healthFunc( *this );
} private:
HealthCalcFunc healthFunc;
};

如今GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针!!!此时使用起来弹性更大:

short calcHealth( const GameCharacter & ); //健康指数计算函数,注意其返回类型为non-int

struct HealthCalculator {
int operator()( const GameCharacter & ) const //为健康计算而设计的仿函数
{...}
}; class GameLevel {
public:
float health( const GameCharacter & ) const; //成员函数,也可用来计算健康指数
... //注意其non-int返回类型
}; class EvilBadGuy : public GameCharacter { //同前
...
}; class EyeCandyCharacter : public GameCharacter { //另一人物类型,假设其构造函数与
//EvilBadGuy相同
...
}; EvilBadGuy ebg1( calcHealth ); //人物1,使用其他函数计算健康指数
EyeCandyCharacter ecc1( HealthCalculator() ); //人物2,使用functer计算健康指数 GameLevel currentLevel;
...
EvilBadGuy ebg2( std::tr1::bind( &GameLevel::health, currentLevel, _1 ) );
//人物3,使用某个成员函数计算健康指数,注意此语法形式!!!

------------

总结本条款提出的几个virtual函数的替代方案:

♢ 使用NVI手法,其以public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数(这是模板方法模式的一种特殊形式)

♢ 将virtual函数替换为“函数指针成员变量”(这是策略模式的一种分解表现形式)

♢ 以tr1::function成员变量替换virtual函数 (这也是策略模式的某种形式)

条款36 绝不重写继承而来的non-virtual函数

记住:

★绝不重新定义继承而来的non-virtual函数

条款37 绝不重新定义继承而来的缺省参数值

记住:

★绝不重新定义继承而来的缺省参数值,∵缺省参数值都是静态绑定,而virtual函数---你唯一应该覆写的东西---却是动态绑定

----------------------------------------------------

本条款的讨论主题是“继承一个带有缺省参数值的virtual函数”。

class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
virtual void Draw( ShapeColor color = Red ) const = ;
...
}; class Rectangle : public Shape {
public:
//注意,赋予了不同的缺省参数值,糟糕!!!
virtual void Draw( ShapeColor color = Green ) const;
...
}; class Circle : public Shape {
public:
virtual void Draw( ShapeColor color ) const;
//以上这么写则当客户以对象调用此函数,一定要指定参数值,∵静态绑定
//下这个函数并不从其base继承缺省参数值。但若以指针或引用调用此函数
//可不指定参数值,∵动态绑定下这个函数会从其base继承缺省参数值
...
}; Shape *pc = new Circle;
Shape *pr = new Rectangle; //pr的静态类型是Shape*,动态类型是Rectangle*!!!
pr->Draw(); //此处可不指定参数值,调用Rectangle::Draw( Shape::Red )

此处pr的动态类型是Rectangle*,∴调用的是Rectangle的virtual函数。Rectangle::Draw函数的缺省参数值应该是Green,但由于pr的静态类型是Shape*∴此一调用的缺省参数值来自Shape class而非Rectangle class!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

--------------

若你试着遵循此条款,且同时提供缺省参数给base和derived class又会怎么样?

class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
virtual void Draw( ShapeColor color = Red ) const = ;
...
}; class Rectangle : public Shape {
public:
virtual void Draw( ShapeColor color = Red ) const;
...
};

这就代码重复了!!!更糟糕的是代码重复又带来了相依性:若Shape内的缺省参数值改变了,所有“重复给定缺省参数值”的那些derived classes也必须改变!!!

--------------------------

当你想令virtual函数表现出你所想要的行为但却遭遇麻烦,聪明的做法是考虑替代设计:

NVI手法

class Shape {
public:
enum ShapeColor { Red, Green, Blue }; //Red是0
void Draw( ShapeColor color = Red ) const { //wrapper!!!
doDraw( color );
}
... private:
virtual void doDraw( ShapeColor color ) const = ;
}; class Rectangle : public Shape {
public:
...
private:
virtual void doDraw( ShapeColor color ) const;
...
};

由于non-virtual函数绝不会被derived classes覆写,这个设计很清楚地使得draw函数的color缺省参数值总是为Red

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

记住:

★复合的意义与public继承完全不同

★在应用域复合意味has-a;在实现域符合意味is-implemented-in-terms-of(根据某物实现出)

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

记住:

private继承意味is-implemented-in-terms-of。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这个设计是合理的

★和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要

-----------------------------------------------------------------------------------------------------

private继承的两个典型特点:

1 若class之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象:

class Person {...};
class Student : private Person {...};
void eat( const Person &p );
Person P;
Student s;
eat( p ); //对
eat( s ); //错误!!!

2 基类中的public和protected成员private继承来之后全变private

-------------------------------------

private继承复合一样意味is-implemented-in-terms-of。但它通常比复合的级别低,即原则上尽量使用复合,必要时才使用private继承,这个必要体现在:空间方面的厉害关系

class Timer {
public:
explicit Timer( int tickFrequency );
virtual void onTick() const;
...
}; class Widget : private Timer {
private:
virtual void onTick() const;
...
};

设计时Timer和Widget并不是将其设计成一个继承体系,即不是is-a关系,我们只想在Widget中用到定时器的onTick而已,所以此处用public继承并不合适,∴选择了private继承。

此设计的一个替代方案如下(使用复合):

class Widget {

private:

class WidgetTimer : public Timer { //类中定义类,可以!!!

public:

virtual void onTick() const;

...

};

WidgetTimer timer; //复合!!!

...

};

下面讲一种必要情况下(即涉及空间利害关系)使用private继承比复合要好:

  这个情况非常激进只适用于所处理的class不带任何数据(这样的class无non-static成员变量;无virtual函数,因为virtual函数会给对象带来vptr;也无virtual base classes):

  class Empty { };

  //此为复合方式

  class HoldsAnInt {

  private:

  int x;

  Empty e; //C++规定凡是独立(非附属)对象必须有非0大小

  };

  这种适用复合设计会有sizeof( HoldsAnInt ) > sizeof( int );

若采用private继承:

  class HoldsAnInt : private Empty {

  private:

  int x;

  };

则有sizeof( HoldsAnInt ) == sizeof( int )。这是所谓的EBO(empty base optimization;空白基类最优化)。这样的实践很少增加derived classes的大小,挺好的!!!

注:现实生活中所指的empty class并不真的是空,其往往含有typedefs、enums、static成员变量或non-virtual函数。

条款40 明智而审慎地使用多重继承(未看)

记住:

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

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

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

EC读书笔记系列之16:条款35、36、37、38、39、40的更多相关文章

  1. EC读书笔记系列之9:条款16、17

    条款16 成对使用new和delete时要采取相同形式 记住: ★若你在new表达式中使用[ ],必须在相应的delete中也使用[ ],反之亦然 -------------------------- ...

  2. EC读书笔记系列之10:条款16、17

    条款18 让接口容易被正确使用,不易被误用 记住: ★“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容 ★“阻止误用”的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的 ...

  3. EC读书笔记系列之1:条款1、条款2、条款3

    条款1:视C++为一个语言联邦 记住: ★C++高效编程守则视状况而变化,这取决于你使用C++的哪一部分 C: Object-oriented c++: Template c++: STL 条款2:尽 ...

  4. EC读书笔记系列之20:条款53、54、55

    条款53 不要轻忽编译器的警告 记住: ★严肃对待编译器发出的警告信息.努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉 ★不要过度依赖编译器的报警能力,∵不同的编译器对待事情的态度 ...

  5. EC读书笔记系列之19:条款49、50、51、52

    条款49 了解new-handler的行为 记住: ★set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用 ★Nothrow new是一个颇为局限的工具,∵其只适用于内存 ...

  6. EC读书笔记系列之18:条款47、48

    条款47 请使用traits classes表现类型信息 记住: ★Traits classes使得“类型相关信息”在编译期可用.它们以templates和“templates特化”完成实现 ★整合重 ...

  7. EC读书笔记系列之17:条款41、42、43、44、45、46

    条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函 ...

  8. EC读书笔记系列之15:条款32、33、34

    条款32 确保你的public继承塑模出is-a关系 记住: ★public继承意味着is-a.适用于base class身上的每一件事情一定也适用于derived class身上,∵每一个deriv ...

  9. EC读书笔记系列之14:条款26、27、28、29、30、31

    条款26 尽可能延后变量定义式的出现时间(Lazy evaluation) 记住: ★尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率 ----------------------- ...

随机推荐

  1. Iterator(迭代器模式)--(超市管理者)

    这个Iterator就是收银台干的活. package patterns.actions.iterator; public interface IteratorList { boolean isEmp ...

  2. 【拆点费用流】【HDU1853】【 Cyclic Tour】

    题意: 有N个城市,M条单向路,Tom想环游全部城市,每次至少环游2个城市,每个城市只能被环游一次.由于每条单向路都有长度,要求游遍全部城市的最小长度. // 给定一个有向图,必须用若干个环来覆盖整个 ...

  3. LeetCode Day2

    Power of Two /** * LeetCode: Power of Two * Given an integer, write a function to determine if it is ...

  4. JeeSite 企业信息化快速开发平台

    平台简介 JeeSite是基于多个优秀的开源项目,高度整合封装而成的高效,高性能,强安全性的开源Java EE快速开发平台. JeeSite本身是以Spring Framework为核心容器,Spri ...

  5. UVA 11175 From D to E and Back

    题意: 给一个n个结点的有向图D,可以构造一个图E:D的每条边对应E的一个结点(例如,若D有一条边uv,则E有个结点的名字叫uv),对于D的两条边uv和vw,E中的两个结点uv和vw之间连一条有向边. ...

  6. 回滚Swtichover

    从11.2.0.2开始,如果由于某种原因switchover没有成功,可以回滚switchover. For physical standby databases in situations wher ...

  7. 本地网址连不上远程mysql问题

    问题:host 'XXX.XXX.XXX.XXX'is not allowed to connect to this MySQL server 解决办法: 进入远程mysql #mysql -u ro ...

  8. Hive的安装配置

    Hive的安装配置 Hive的安装配置 安装前准备 下载Hive版本1.2.1: 1.[root@iZ28gvqe4biZ ~]# wget http://mirror.bit.edu.cn/apac ...

  9. php 数组去除空值

    /** * 方法库-数组去除空值 * @param string $num 数值 * @return string */ public function array_remove_empty(& ...

  10. SQL Server 改变数据库的名字

    方法 1: alter database modiry name = new_database_name; ---------------------------------------------- ...