条款 31 : 将文件间的编译依存关系降至最低
Minimize compilation dependencies between files
C++没有把“将接口从实现中分离”这件事做得很好。Class的定义不止是叙述了类接口,还包括了十足的实现细节。
例如下面代码:
#include"Date"
#include<string>
class Person{
public:
Person();
std::string name()const;
std::string date()const;
private:
std::string theName;//实现细目
Date BirDate;//实现细目
}
很不幸,上面的申明方式使得Person类的定义文件引入了其他文件,这就形成了一种编译依赖关系。任何其他文件的改变都将导致Person文件需要从新编译。
或许你会奇怪C++为什么坚持将class 的实现细目放在class定义式内,而不是用下述代码方式:
namespace std{class string;}//前置声明,错误
class Date;//前置声明
class Person{
public:
Person();
std::string name()const;
std::string date()const;
...
}
如果采用这种前置声明的方式,也就不存在编译依赖关系了。但是该方法是错误的。第一,string并不是一个class;第二,**“前置声明每一个东西”的困难是编译器必须在编译期间就知道对象的大小。**但是不将实现细目放进class内,编译器又从何得知呢。这种问题在Java中并不存在,因为其定义对象时只不过是给其分配了一个指针罢了。这是一个好的策略,也就是常说的pimpl策略。
Pimpl策略的关键在于用“声明的依存性”替换“定义的依存性”,这正是编译依赖性最小化的本质:实现中让头文件尽可能自我满足,如果做不到。则让他与其他文件内的声明式(而不是定义式)相互依存。即:
- 如果使用object reference或object pointer可以完成任务,就不要使用object.
- 如果可以,尽量以class声明替换class的定义。
- 为声明式和定义式提供不同的头文件。
- 还有一种方法就是定义Person为虚基类,也就是interface class。这样的类的功能类似Java中的Interface。
请记住
- 支持“编译依存性最小化”的一般构想是:相依于申明式,不要相依与定义式。基于此构想的两个手段是Handle class 和Interface class.
- 程序头文件应该以“完全且仅有声明式”(full and declaration only forms)的形式存在。这种方法无论是否涉及template都适用。