您的位置:首页 > 编程语言 > C语言/C++

Effective C++ 第六章--继承与面向对象设计笔记

2017-01-19 18:48 281 查看
条款32确定你的public继承塑模处is-a关系

条款33避免遮掩继承而来的名称

条款34区分接口继承和实现继承

条款35考虑virtual函数以外的其他选择
藉由Non-Virtual Interface手法实现Template Method模式

藉由Funciton Pointers实现Strategy策略模式

藉由tr1function完成Strategy模式

古典的Strategy模式

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

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

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

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

条款40明智而审慎的使用多重继承



条款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”的两相结合。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  继承