条款31:将文件间的编译依存关系(compilation dependencies)降至最低
- 对于C++类而言,如果它的头文件变了,那么所有这个类的对象所在的文件都要重编,但如果它的实现文件(cpp文件)变了,而头文件没有变(对外的接口不变),那么所有这个类的对象所在的文件都不会因之而重编
1.接口与实现分离(通过pimpl idiom的设计方式)——其实质在于以”声明的依存性“替换”定义的依存性“
- 上述也是编译依存最小化的本质——现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式相依
2.编译依存最小化核心的设计策略:
-
如果使用object references或object pointers可以完成任务,就不要用objects
-
如果能够,以class声明式替换class定义式
-
为声明式和定义式提供不同的头文件
3.基于上述策略的方式是——制作handle class(也就是通过pimpl idiom的设计方式)
-
方法一:将它们的所有函数转交给相应的实现类(implementation classes)并交由后者完成实际工作
- 在.h文件中用class 声明代替include头文件,把成员变量替换为指针的形式,此时任何接口类头文件产生的变化只会导致接口类头文件的变化而重新编译,以及Person实现文件由于include了接口类的头文件也要重新编译;而Person类头文件由于只使用了类的声明式,所以并不会重新编译,因此所有使用Person类的对象的文件也都不需要重新编译了,这样就大大降低了文件之间的编译依存关系
-
方法二:令该类成为一种特殊的abstract base class(抽象基类),称为interface class(接口类)
-
父类只提供虚方法,而将实现放置在子类中,再通过父类提供的一个特别的静态函数,生成子类对象,通过父类指针来进行操作;从而子类头文件的改动也不会导致使用该类的文件重新编译,因为用的是父类指针,客户include的是只是父类头文件。
-
例子:
std::tr1::shared_ptr<Person> Person::Create(const std::string& name, const Date& birthday, const Address& addr) { return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday, addr)); }
-
4.handle class和interface class解除了接口和实现之间的耦合关系,从而降低了文件之间的编译依存性
5.使用上述的两个方法解除耦合有好处,那就是降低了代码的维护成本,但是也有缺点:
- 前者的缺点是动态内存带来的额外开销
- 后者的问题是virtual使用时vptr的性能损失
6.请记住:
- 支持“编译依存最小化”的一帮构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是handle classes和interface classes
- 程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是设计templates都适用
第六章:继承与面向对象设计(Inheritance and Object-Oriented Design)
条款32:确定你的public继承塑模出is-a关系
1.以C++进行面向对象编程,最重要的一个规则就是:public inheritance(公开继承)是一种”is-a“的关系
2.请记住:
- “public继承”意味着is-a的关系。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象