条款三十一: 将文件间的编译依赖降至最低
当我们实现定义一个Person
类时
1 |
|
由于前面的include
会导致Person
的定义文件和其包含的头文件形成了编译依赖关系,如果头文件中有所改变,就会导致任何使用Person
类的文件要重新编译。考虑用类的声明,替换include
, 即替换定义。
为了将对象的实现细节和对象的定义分离开来,使用pimpl模式。
1 |
|
通过这样的设计,使用Person
的客户就完全与Date
,Address
和Person
的实现细节所分离。
接口与实现分离的思想:
如果能用对象的引用或指针完成任务,就不要使用对象本身。
尽量以
class
的声明式代替定义式(include
)为声明式和定义式提供不同的头文件,提供这样的两个头文件是程序库作者的任务。
1
2
3
Date today();
void clearAppointments(Date d);
另一个实现这种分离的方式是把Person
定义成虚基类。
- 支持编译依赖最小化的思想是:不依赖于定义式,依赖于声明式。有两种方式实现,pimpl设计模式,虚基类
- 程序库头文件应该以仅有声明式的形式存在,包括设计template的情况()。
条款三十二:确保public继承构建出的是“is-a”的关系
以C++进行面向对象编程,public继承意味着“is-a”的关系。意思是如果以class D继承class B,你所表达的含义是每一个类型为D的对象同时也是一个类型为B的对象,需要B的地方,D对象可以使用,反之却不成立。
但我们在实现时却不能单凭直觉就这样做。举两个例子:
企鹅是一种鸟,同时,鸟可以飞,如果单纯的描述这样的事情:
1
2
3
4
5
6
7
8class Bird{
public:
virtual void fly();
};
class Penguin:public Bird{
...
}这时,出现了企鹅可以飞的错误,在说鸟可以飞的时候,要注意并不是所有的鸟都可以飞。
对于这个问题,这样设计显然更好:
1
2
3
4
5
6
7
8
9
10
11class Bird{
... //没有fly函数
}
class FlyingBirt: public Bird{
public:
virtual void fly();
...
};
class Penguin: public Bird{
... //没有fly函数
}我认为最好还是使用这种在编译器拒绝错误的方式。
正方形类是否应该继承矩形类?
按照我们在学校学过的,正方形是一种矩形,我们会得到这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Rectangle{
public:
virtual void setHeight(int newHeight);
virtual void setWeidth(int newWidth);
virtual int height() const;
virtual int width() const;
};
void makeBigger(Rectangle& r) //增加r的面积
{
int oldHeight = r.height();
r.setWidth(r.width() + 10); //r的宽度加10
assert(r.height() == oldHeight);
}
class Square: public Rectangle{...};
Square s;
assert(s.width() == s.height());
makeBigger(s);
assert(s.width() == s.height());显然,在makeBigger后,出现了问题,正方形不再是正方形。所以以is-a表述正方形和矩形的关系并不正确。
is-a并不是唯一存在于class之间的关系,还有has-a和is-implemented-in-terms-of。正确并准确地实现这些关系是C++的OOP中很重要的部分。
- public意味着is-a,适用于基类身上的每一项操作也要适用于派生类。
条款三十三:避免遮掩继承而来的名称
这个条款其实是有关于作用域的问题。
在函数中会发生局部变量遮盖全局变量的现象:
1 | int x; |
这时cin
操作的对象会是函数内定义的double x
。
类似的,类在进行继承时也会出现这种名称相同覆盖的现象。
1 | class Base{ |
这里不论函数是虚函数还是非虚函数,都会发生这种同名覆盖现象,特别是在派生类中重载了基类的函数时,就不能再调用基类的函数,这不符合我们前面提到的is-a关系。为了防止这种遮盖现象,可以使用using
1 | class Derived: public Base{ |
现在就不会再出现上面的错误。
如果不想继承基类的所有函数(在非public继承下),这时要用到转交函数:
1 | class Base{ |
这时派生类只能调用无参版本的mf1
函数。
- 派生类内的名称会对基类的同名函数进行掩盖,在public继承下可以使用using避免出现这样的情况。
- 转交函数可以在不想继承基类所有函数的情况下使用。