Effective C++(条款35-38)

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

当你为解决问题而寻找某个设计方案的时候,不妨考虑virtual函数的替代方案,下面是几个替代方案:


1、使用non-virtual interface(NVI)手法,那是template method设计模式的一种特殊形式。它以 public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数

class GameCharacter{
public:
      int healthValue()const //继承类不重新定义它
      {
            ...    //做一些事前工作
            int retVal=doHealthValue();
            ...    //做一些事后工作
            return retVal;
      }
      ...
private:
      virtual int doHealthValude() const;//继承类可重新定义它
      {
             ...   //缺省算法,计算健康指数
      }
};

NVI手法的一个优点就是可以在代码中做一些“事前工作”及“事后工作”。“事前工作”可以包括锁定互斥器、制造运转日志记录项、验证class约束条件、验证函数先决条件等等。“事后工作”可以包括互斥器解除锁定、验证函数事后条件、再次验证class约束条件等等。如果你让客户直接调用virtual函数,就没有任何好办法可以做这些事情。



2、将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式。

class GameCharacter;//前置声明
//以下函数是计算健康指数的缺省算法
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
      typedef int(*HealthCakcFunc) (const GameCharacter&);//定义一个返回类型为int,以GameCharacter的引用为参数的函数类型,这个函数类型的别名是HealthCalcFunc
      explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):healthFunc(hcf)
      {}
      int healthValue() const
      {    return healthFunc(*this);}
      ...
private:
      HealthCalcFunc healthFunc;
};

运用函数指针替代virtual函数,有这样的优点:每个对象各自拥有自己的健康计算函数,而且可以在运行期改变计算函数。缺点是可能必须降低GameCharacter封装性。




3、以trl::function成员变量替换virtual函数,因此允许使用任何可调用物搭配搭配一个兼容于需求的签名式。

class GameCharacter; 
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
      typedef std::trl::function<int(const GameCharacter&)> HealthCalcFunc;/*定义一个返回类型为int,以GameCharacter的引用为参数的函数类型,这个函数类型的别名是HealthCalcFunc,它是任何“可调用物”,可被调用并接受任何兼容于GameCharacter之物,返回任何兼容于int的东西*/
      explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):healthFunc(hcf)
      {}
      int healthValue() const
      {    return healthFunc(*this);}
      ...
private:
      HealthCalcFunc healthFunc;
};




4、 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。

class GameCharacter;
class HealthCalcFunc{
public:
      ...
      virtual int calc(const GameCharacter& gc) const
      {...}
      ...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter{
public:
      explicit GameCharacter(HealthCalcFunc* phcf=&defaultHealthCalc):pHealthCalc(phcf)
      {}
      int healthValue() const
      {  return pHealthCalc->calc(*this);}//每一个GameCharacter对象内都含有一个指向HealthCalcFunc继承体系的对象的virtual函数
      ...
private:
      HealthCalcFunc* pHealthCalc;
};


请记住:

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

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



条款36:绝不重新定义继承而来的non-virtual函数

class B{
public:
      void mf();
      ...
};
class D:public B{...};
D x;
B* pB=&x;
pb->mf();
D* pD=&x;
pD->mf();
//如果D中定义了自己版本的mf()
class D:public B{
public:
      void mf();//遮掩了B::mf()
      ...
};
pB->mf();//调用B::mf()
pD->mf();//调用D::mf()

non-virtual函数如B::mf和D::mf都是静态绑定的。这个意思就是说,由于pB声明为一个指向B的指针,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为D的对象。

但是,virtual函数确实动态绑定的。如果mf()是virtual函数,不论通过pB还是pD调用mf,都会导致调用D::mf,因为两者真正指向的都是一个类型为D的对象。


如果你为继承类对象重新编写non-virtual函数,继承对象可能展现出不一致的行径。当函数被调用的时候,任何一个继承类对象都可能表现出继承类或基类的行为:决定因素不在对象本身,而在于指向该对象的指针或引用当初的声明类型。所以,任何情况下都不该重新定义一个继承而来的non-virtual函数




条款37:绝不要重新定义继承而来的带有缺省参数值的virtual函数

本条款成立的理由非常明确:virtual函数是动态绑定,而缺省参数值是静态绑定

静态绑定又名前期绑定,动态绑定又称后期绑定。对象的所谓静态类型,就是它在程序中被声明时所采用的类型;对象的所谓动态类型则是指“目前所指对象的类型”,也就是说动态类型可以表现出一个对象将会有什么行为。


class Shape{
public:
      enum ShapeColor{red,green,blue};
      virtual void draw(ShapeColor color=red) const=0;
      ...
};
class Rectangle:public Shape{
public:
      virtual void draw(ShapeColor color=green) const;//注意!赋予不同的缺省参数值,这真糟糕
      ...
};
class Circle:public Shape{
public:
      virtual void draw(ShapeColor color) const;/*请注意:这么写的话,当客户以对象调用此函数一定要指定参数值。因为静态绑定下这个函数并不从其基类继承缺省参数值。但若是以指针或引用调用此函数,可以不指定参数值,因为动态帮顶下这个函数会从基类继承缺省参数值*/
      ...
};


现在考虑这些指针:

Shape* ps; //ps没有动态类型
Shape* pc=new Circle;
Shape* pr=new Rectangle;//静态类型都是Shape*
ps=pc;
ps=pr;//动态类型是可以在程序执行过程中改变的(通常由赋值动作)
pc->draw(Shape::red);//调用Circle::draw(Shape::red)
pr->draw(Shape::red);//调用Rectangle::draw(Shape::red)

virtual函数是动态绑定而来的,意思是 调用一个virtual函数时,究竟调用哪一份函数实现代码, 取决于发出调用的那个对象的动态类型

现在考虑带有缺省参数值得virtual函数。上文说过,virtual是动态绑定,但缺省参数值确实静态绑定的,意思是你可能会在”调用一个定义于继承类中的virtual函数“的同时,却是用基类中为它所指定的缺省参数值。

pr->draw();//调用Rectangle::draw(Shape::red)


pr的动态类型是Rectangle*,所以调用的是Rectangle中的virtual函数,但是pr的静态类型是Shape*,所以此调用的缺省参数值来至于Shape类。即使把 指针换成 引用问题任然存在。 为什么virtual继承类中的缺省参数值被重新定义却在缺省调用的情况下调用的是基类中的缺省参数值?

答案是运行期效率!如果缺省参数值是动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的缺省参数值。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍。


此外,如果你试图遵守这条规则,并将基类中的缺省参数值提供给继承类用户,这样会引起代码重复的问题。更糟糕的是,代码重复又带来相依性,如果基类中的缺省值改变了,所有重复给定缺省值的继承类中缺省参数值也必须改变,否则最终还是会导致上文中讨论的”重复定义一个继承而来的缺省参数值“问题。


virtual函数是你唯一应该覆写的东西!





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

复合的意义与public继承完全不同,它是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。

在应用域,复合意味着has-a(有一个)。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)

程序中的对象其实相当于你所塑造的世界中的某些事物,例如人、汽车、视频画面等,这样的对象属于应用域部分;其他对象则纯粹是实现细节上的人工制品,像缓冲区、互斥器、查找书等等,这些对象相当于软件中的实现域。


has-a关系示范:

class Address{...};
class PhoneNumber{...};
class Person{
public:
      ...
private:
      std::string name;
      Address address;
      PhoneNumber Number;
};


is-implemented-in-terms-of示范关系:

/*希望制造出一组classes用来表示由不重复独享组成的sets,标准库中的set template的实现往往招致每个元素耗用三个指针的开销,我们可以使用list template实现Set*/
template<class 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;
};

Set成员可以大量依赖list及标准程序库其他部分提供的机能来完成。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值