编译依存的问题——170
通常,我们的定义的文件包含很多头文件,这些文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中有任何一个被改变,或这些头文件所依赖的其他头文件有任何改变,那么每一个含入我们定义的文件就得重新编译,任何使用我们定义的文件也必须重新编译。这样的连串编译依存关系会对许多项目造成难以形容的灾难。
使用指针指向类——172
我们可以将Person分割为两个calsses,一个只提供接口,另一个负责实现该接口:
#include<string>
#include<memory>
class PersonImpl; //Person实现类的前置声明
class Date; //Person接口用到的class的 前置声明
class address;
class Person{
public:
Person(const std::string& name,const Date& birhday,const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::tr1::shared_ptr<PersonImpl> pImpl;
};
在这里,main clss(Person)只内含一个指针成员,指向其实现类(PersonImpl)。这样的设计之下,Person的客户就完全与Dates,Address以及Persons的实现细目分离了。那些classes的任何实现修改都不需要Person客户端重新编译。
我们有如下设计策略:
(1)如果使用object references 或 object pointers可以完成任务,就不要使用objects。
(2)如果能够,尽量以class声明式替换class定义式。
(3)为声明式和定义式提供不同的头文件。
制作Handles classes的两种办法——174
第一种办法就如上节所述使用pimpl idiom的classes。这种办法是将它们所有函数转交给相应的实现类(implementation classes)并由后者完成实际工作。例如:
#inlcude "Person.h"
#inlcude "PersonImpl.h" //PersonImpl有着和Person完全相同的成员函数,两者接口完全相同。
Person::Person(const std::string& name,const Date& birthday,const Address& addr)
:pImpl(new PersonImpl(name,birthday,addr)) {}
std::string Person::name() const
{
return pImpl->name();
}
第二种办法令Person成为一种特殊的抽象基类,称为Interface class。这种class通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数(C++ Primer 5th,540),用来叙述整个接口。一个针对Person而写的Interface class如下:
class Person{
public:
virtual ~Person();(C++ Primer 5th,540)
virtual std::string name() const=0;
virtual std::string birthDate() const=0;
virtual std::string address() const=0;
...
};
具体实现由客户定义。
不论Handle classes或Interface classes,一旦脱离inline函数都无法有太大作为。(条款30)
总结——178
(1)支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
(2)程序头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。