首页 > 代码库 > effective c++条款32~40“继承与面向对象设计”整理

effective c++条款32~40“继承与面向对象设计”整理

条款32:确定你的public继承塑模出is-a关系

以C++进行面向对象编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。
在C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。is a并不是唯一存在classes之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-in-term-of(根据某物实现出)。
请记住:
“public继承”意味is-a。适用于base class身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。

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

在继承体系下,派生类的作用域包含在基类作用域下,因而在派生类中的同名变量会遮掩基类的变量。

class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};

class Derived : public Base{
public:
virtual void mf1();
void mf3();
void mf4();
...
};

Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //error
d.mf2(); //ok,调用Base::mf2
d.mf3(); //ok,调用Derived::mf3
d.mf3(x); //error

在基类中所有名为mf1和mf3的函数都被派生类中的同名函数遮掩掉了,即使它们有不同的参数类型,即使它们是虚函数或非虚函数。
由于上面是public继承,为了能够使用基类中的同名函数(也就是保持is-a关系),可以采用两种办法:使用using声明式或转交函数。

1.在上面的例子中,派生类可做以下修改:
class Derived : public Base{
public:
using Base::mf1; //让Base class内名为mf1和mf3的所有东西中
using Base::mf3; //在Derived作用域中可见
virtual void mf1();
void mf3();
void mf4();
...
};
Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //OK,调用Base::mf1(int)
d.mf2(); //ok,调用Base::mf2()
d.mf3(); //ok,调用Derived::mf3()
d.mf3(x); //ok,调用Base::mf3(int)
这就意味着如果继承基类并想重载基类函数,而你又希望重新定义或覆写其中一部分,那么为那些原本会被遮掩的每个名称引入以一个using声明式。

2.如果试图选择性地只继承部分重载函数,这在public继承下不可能发生,因为它违反了public继承所暗示的is-a关系,然而在private继承下可能有意义。例如上式Derived以private继承base,而Derived唯一想继承的mf1是那个无参数版本。using声明式这里没用,因为他声明的某给定名称的所有同名函数都

在Derived中可见,可以使用转交函数(forwarding function):
class Derived : private Base{ //私有继承,is-implemented-in-term-of关系
public:
virtual void mf1(){ //转交函数
Base::mf1();
}
...
};

Derived d;
intx;
...
d.mf1(); //ok,调用Derived::mf1()
d.mf1(x); //error,Base::mf1被屏蔽

请记住:
derived calsses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。 
为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding function)。

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

表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。成员函数的接口总是会被继承。
pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。所以:声明一个pure virtual函数的目的是为了让derived class只继承函数接口。
令人意外的是,我们竟然可以为pure virtual函数提供定义。但调用它的唯一途径是“调用时明确指出其class名称”:

声明简朴的(非纯)impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。
如果成员函数是个non-virtual函数,意味着它并不打算在derived classes中有不同的行为。

请记住:
接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
pure virtual函数只具体制定接口继承。
简朴的(非纯)impure virtual函数具体制定接口继承及缺省实现继承。
non-virtual函数具体制定接口继承以及强制性实现继承。

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

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

请记住:
绝对不要重新定义继承而来的non-virtual函数。

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

请记住:
绝对不要重新定义继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

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

请记住:
1.private继承意味is-implementation-in-terms of(根据某物实现出)。她通常比复合级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

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

请记住:
1.多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需求。
2.virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base class不带任何数据,将是最具实用价值的情况。
3.多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。

effective c++条款32~40“继承与面向对象设计”整理