【effective C++第六章下部】
条款35:考虑virtual函数以外的其他选择
打算写一个继承体系
class GameCharacter
{
public:
virtual int healthvalue() const;
....
};
有一个有趣的思想流派,这个流派主张virtual 函数应该几乎是private.较好的设计是保留healthvalue为public函数,但让他成为non-virtual,并调用用一个private virtual 函数进行实际工作。
class GameCharacter{
public:
public:
int healthvalue() const {
.... //做一些事前工作
int retVal=doHeathvalue();//做正正的工作
...
return retVal;//做一些事后工作
}
...
private:
virtual int doHealth() const
{
...
}
;
这种做法很牛逼,刚开始看感觉没什么,可能是自己开发经验少,没有用武之地,建议读者自行百度公有虚函数和私有虚函数的区别,私有虚函数在父类中存在可以确定虚函数执行顺序,避免某些业务不正确的操作流程。
具备多态性质的虚函数一定得是public,因此就用不上这样的原则。
借由函数指针实现策略模式
例如我们可能会要求每个用户的构造函数接受一个指针,指向一个健康计算函数,而我们可以调用该函数进行实际运算。
class GameCharacter; //前置声明
//以下是函数计算健康指数的缺省算法。
int defaultHealthCalc(const GameCharacter & gc);
class GameCharacter {
public:
typedef int (&healthCalcFunc) (const GameCharacter &);
explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
:healthFunc(hcf)
{}
int healthvalue () const
{
return healthFunc(*this);}
.....
private:
HealthCalcFunc healtjFunc;
};
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
class EviBadGuy:public GameCharacter {
public:
explict EvilBadGuy(HealthCalcFunc hcf= defaultHealthCalc)
:GameCharacter(hcf)
{...}
.............
};
一般而言,唯一能够解决“需要以non-menber函数访问class的non-public成分的办法是:弱化class的封装。例如class可声明那个non-menber为友元,或是为其实现的某一部分提供public访问函数,也可以采用函数指针替换虚函数,其优点是可各自拥有自己的健康计算函数和可在运行期改变计算函数”
借由tr1::function 完成Strategy模式
一旦习惯了templates以及它们对隐式接口的使用,基于函数指针的做法就显得有些呆板了。
tr1::function:
class GameCharacter ;
int defaultHealthCalc(const GameCharacter&gc);
class GameCharacter {
public:
//HealthCalcFunc可以是任何调用物
typedef std::tr1::functtion<int (const GameCharacter&)>HealthCalcFunc;
explict GameCharacter(HealthCalcFunc hcf= defaultHealthCalc)
:healthFunc(hcf)
{} int healthValue() const
{return healthFunc(*this);}
.../////
private:
HealthCalcFunc healthFunc;
};
条款36:绝不重新定义继承而来的非虚函数
如果需要重新定义,最好把它声明为虚函数
条款37:绝不重新定义继承而来的缺省参数值
虚函数系动态绑定,缺省参数值是静态绑定
如果你试图遵守条款37,可能会有这种问题
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 =Red) const;
...
};
代码重复,更糟糕的是又带着相依性,如果父类中的缺省参数改变了,所有重复给定缺省参数值的那些子类也必须改变,否则会导致重复定义一个继承而来的缺省参数值。
想想条款35替换虚函数的设计方案
class Shape {
public:
enum ShapeColor {Red ,Green,Blue};
void draw (ShapeCollor color =Red) const
{
doDraw(color);
}
....
private:
virtual void doDraw (ShapeColor color ) const =0;
};
class Rectangle :public Shape {
public:
..
private:
virtual void doDraw(ShapeColor color) const;
...
};
由于非虚函数绝对不会被子类覆写,这个设计很清楚的使得draw函数的color缺省参数总是为Red。
条款38:通过复合塑模出has-a或根据某物实现出
复合是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系
class Address{...}
class PhoneNumber{...}
class Person { }
public :
...
private:
std::string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
比较麻烦的是区分is a和is-implemented-in-terms-of(根据某物实现出)这两种对象关系。假设你需要一个template,希望制造出一组classes用来表现由不重复对象组成的sets。由于reuse是件美妙无比的事情,直觉是采用标准库提供的set template。
是的,如果他人写的template合乎要求,又何必再重写一个呢?
不幸的是set的实现往往招致“每个元素耗用三个指针”的额外开销。因为sets通常以平衡查找树实现而成,使他们在查找、安插、移除元素时保证拥有对数时间效率。当速度比空间重要,这是个通情达理的设计,但当空间比速度更重要时,似乎需要
重写自己的template.
实现sets的方法太多了,有一种就是底层用链表,而标准库中刚好就有链表模板。
template<typename T> //将list应用于set错误做法。
class Set:public std::list<T>{...};
这两个class之间并非是is a的关系,所以用public继承不适合塑模出他们。正确的做法是set对象可根据list对象实现出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的数据
};
条款39:明智而审慎地使用private继承
如果classes之间的继承关系是private,编译器不会自动将一个子类对象转换为一个父类对象
class Empty { }//没有数据,所以其对象应该不使用任何内存
class HoldsAnInt {
private:
int x;
Empty e;//应该不需要任何内存
};
空类默认安插一个char到空对象内
当你面对并不存在is-a关系的两个classes,其中一个需要访问另一个protected
成员,或需要重新定义其一或多个virtual 函数,private继承极有可能成为正统设计策略。
条款40:明智而审慎地使用多重继承
public继承似乎都可以换成virtual public继承,但后果就是你必须为虚继承付出代价。
当一个新的子类加入继承体系中,它必须承担其虚基类的初始化责任。
非必要不要使用虚基类,平常请使用非虚继承。第二,尽量避免在虚基类中放置数据。这样一来就不必要担心这些类身上的初始化和赋值所带来的诡异事情。
多继承的确有正当用途,其中一个情节涉及public继承某个Interface class 和“private 继承某个协助实现的class”的两相组合。