条款32:确定你的public继承塑模处is-a关系
- “public继承”意味着is-a。适用于每个基类身上的每一件事一定也适用于派生类身上,因为每一个派生类对象也都是一个基类对象。
条款33:避免遮掩继承而来的名称
派生类继承自基类,会产生命名作用域嵌套。派生类内部自成一个作用域,如果派生类使用某个名字,首先会在派生类命名作用域查找。如果派生类内部没有该名字,才会在基类的作用域类查找该名字。如果基类也没有,再去namespace,甚至全局作用域。
派生类继承自基类后,如果定义一个函数,函数名名与基类中函数名相同,那么基类中的所有同名函数会被覆盖。无论参数是否一致。因为这是命名规则。
如果派生类继承基类,基类有同名且重载多次的函数,派生类希望重新定义或者覆盖其中一部分,使用using。使用using可以使基类一部分和派生类重载函数同时存在,而不致使基类所有同名函数全部被覆盖。
如:
class base {
public:
void fun(int i) { std::cout<<"base"<<std::endl; }
};
class derived : public base{
public:
using base::fun;
void fun() { std::cout<<"derived"<<std::endl; }
};
如上用法,则不会产生全部覆盖。派生类可以根据不同参数选择调用基类或是自己的函数。
有时候派生类不想继承基类的全部函数,这时候,不能用公有继承,因为公有继承是is-a。此时使用using不可行,因为using会使基类中所有同名函数都被派生类可见。我们需要使用private继承加转交函数来实现。
class derived : private base { //私有继承,只继承实现
public:
virtual void fun() { base::fun(); }
};
上面代码仅为示意,以这种形式书写,就可调用基类某个函数。
- 派生类内的名称会遮掩基类内的名称。在公有继承下从来没有人希望如此。
- 为了让遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。
条款34:区分接口继承和实现继承
- 接口继承和实现继承不同。在公有继承下,派生类总是继承基类的接口。
- 纯虚函数只具体指定接口继承
- 虚函数(非纯)具体指定接口继承及缺省实现继承。(意思是虚函数自身定义了一份默认实现,如果子类不重写的话,那使用的就会是这份基类的实现)。
- 普通函数具体指定接口继承以及强制性实现继承。(公有继承下普通函数是不能被重写的,虽然没有语法错误,但不符合is-a,所以是强制性实现)。
条款35:考虑virtual函数以外的其他选择
藉由Non-Virtual Interface手法实现Template Method模式
大概意思就是virtual函数要作为private函数,使用一个non-virtual函数调用private virtual函数。
比如:
class game_character {
public:
int health_value() const {
... //做一些前期工作
int ret = do_health_value(); //做真正的工作
... //做一些后期处理
return ret;
}
private:
virtual int do_health_value() const { //派生类可重新定义它,只不过不能使用
...
}
};
这一设计,又称non-virtual interface(NVI)手法。它是所谓Template Method设计模式的一种形式。上面的non-virtual函数可以称作wrapper。
NVI手法的一个优点在于可以做事前,事后的工作。比如事前加锁,事后解锁,assert验证等。
藉由Funciton Pointers实现Strategy(策略)模式
实际上就是针对不同需求使用不同函数指针进行回调,不赘述。
藉由tr1::function完成Strategy模式
这个实际上是std::function,或者说boost::function,这本书比较老,所以当时还没有。
std::function相对函数指针的优势在于全能!不仅可使用函数,也可食用函数对象,甚至成员函数。不过要注意与std::bind的配合,绑定成员函数需要改该型对象指针。
古典的Strategy模式
利用纯代码实现策略模式:
class game_character; //前向生命
class health_calc_func {
public:
virtual int calc(const game_character& gc) const
{ ... }
};
health_calc_func default_health_calc;
class game_character {
public:
explicit game_character(health_calc_func* phcf = &default_health_cal) : health_calc_(phcf)
{}
int health_value() const {
return health_calc_->calc(*this);
}
private:
health_calc_func* health_calc_;
};
上述就是策略模式(策略模式真平易近人),用上述方法只要为health_calc_func继承体系纳入一个派生类,就可以添加一个新的算法了。
条款36:绝不重新定义继承来的non-virtual函数
这个不必说,因为要符合is-a。
条款37:绝不重新定义继承而来的缺省参数值
代码验证:
class base {
public:
virtual void fun(int i = 3) { std::cout<<i<<std::endl; }
};
class derived : public base {
public:
virtual void fun(int i = 2) { std::cout<<"derived"<<std::endl; std::cout<<i<<std::endl; }
};
int main()
{
base *b = new derived;
b->fun();
return 0;
}
上述代码打印的结果是:
derived 3
这是完全错误的结果,调用了派生类函数,却打印出基类的默认值。
- 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。
条款38:通过复合塑模处has-a或”根据某物实现出”
复合有has-a和”is-implement-in-terms-of”两种。has-a就是某物有某个成员,比如教室,成员变量就是桌子等,这个好理解。主要来说后者,意思是根据某物实现出。比如自己实现一个数据结构集合set,我们使用链表list来做它的底层实现,那么不应该用继承自list(这会构成is-a,明显不符合),应该把list作为set的成员变量。成员函数在链表上进行相应的简单操作就可以实现set的功能,所以说set是根据list实现出。
- 复合(composition)的意义和public继承完全不同。
- 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合一位is-implemented-in-terms-of(根据某物实现出)。
条款39:明智而审慎的使用private继承
- private继承意味着”根据某物实现出”。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
- 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于”对象尺寸最小化”的程序库开发者而言,可能很重要。(这意思是,基类如果是空类,非空派生类继承基类,基类的那一个char占位字节会被去掉。实际上,这本书太老了,即便不使用private继承,目前所有编译器都会针对这种情况优化,公有继承也同样。所以这里是错的。)
我们可以使用复合来代替私有继承,如下:
class base {
private:
virtual void timer() { std::cout<<"base"<<std::endl; }
};
class derived {
public:
void call() { timer_.timer(); }
private:
class inside_class : public base {
public:
virtual void timer() { std::cout<<"inside_timer"<<std::endl; }
};
inside_class timer_;
//如果derived继承自base,此处可以重写:
//virtual void timer() { std::cout<<"derived"<<std::endl; }
//注意本段代码derived并没有继承base,上面一行代码仅为说明:如果继承,基类私有虚函数虽然能重写,但是是不可调用的。
};
int main()
{
derived d;
d.call();
return 0;
}
并且利用上述技巧我们可以实现不能被派生类定义的虚函数(注意去掉注释):
如果要类A继承某个类,要实现它的虚函数,我们使用一个成员类公有继承它,并包含一个该成员类的对象。我们就可以在成员类中重写改虚函数,调用该虚函数通过成员类对象即可。那么,往后如果某个类B继承类A,那么它是不可以定义上述虚函数的,因为那是类A私有对象的函数。
条款40:明智而审慎的使用多重继承
- 多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
- virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
- 多重继承的确有正当用途。其中一个情节设计”public继承某个Interface class”和”private 继承某个协助实现的class”的两相结合。