派生类继承自基类,会产生命名作用域嵌套。派生类内部自成一个作用域,如果派生类使用某个名字,首先会在派生类命名作用域查找。如果派生类内部没有该名字,才会在基类的作用域类查找该名字。如果基类也没有,再去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(); }
};
上面代码仅为示意,以这种形式书写,就可调用基类某个函数。
大概意思就是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验证等。
实际上就是针对不同需求使用不同函数指针进行回调,不赘述。
这个实际上是std::function,或者说boost::function,这本书比较老,所以当时还没有。
std::function相对函数指针的优势在于全能!不仅可使用函数,也可食用函数对象,甚至成员函数。不过要注意与std::bind的配合,绑定成员函数需要改该型对象指针。
利用纯代码实现策略模式:
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继承体系纳入一个派生类,就可以添加一个新的算法了。
这个不必说,因为要符合is-a。
代码验证:
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
这是完全错误的结果,调用了派生类函数,却打印出基类的默认值。
复合有has-a和”is-implement-in-terms-of”两种。has-a就是某物有某个成员,比如教室,成员变量就是桌子等,这个好理解。主要来说后者,意思是根据某物实现出。比如自己实现一个数据结构集合set,我们使用链表list来做它的底层实现,那么不应该用继承自list(这会构成is-a,明显不符合),应该把list作为set的成员变量。成员函数在链表上进行相应的简单操作就可以实现set的功能,所以说set是根据list实现出。
我们可以使用复合来代替私有继承,如下:
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私有对象的函数。