20180320 C++ 将文件间的编译依存关系降到最低
当对C++程序的某个class实现文件做了些微修改,再重新建置这个文件,会花费很长时间,,因为C++没有把“将接口从实现中分离”这事做的很好。Class的定义式不只详细叙述了class接口,还包括十足的实现细节,eg:
class Person{
public:
Person(const std::string& name,const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName; //实现细节
Date theBirthDate; //实现细节
Address theAddress; //实现细节
}
这里的class Person无法通过编译--若编译器没有取得其实现代码所用到的class string,Data和Address的定义式。这样的定义式通常由#include指示符提供,所以Person定义文件的最上方很可能存在这样的东西:
#include<string>
#incude"date.h"
#include"address.h"
这样就是在Person定义文件和其含入文件之间形成了一种编译依存关系(compilation dependency),若这些头文件中有任何一个被改变,或这些头文件所依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。
针对Person可以这样做:把Person分割为两个classes,一个提供接口,另一个负责实现接口。若负责实现的那个所谓implementation class取名为PersonImpl,Person将定义如下:
#include<string>//标准程序库组件不该被前置声明
#include<memory>//此乃为了tr1::shared_ptr而含入
class PersonImpl;//Person实现类的前置声明
class Date;//Person接口用到的classes的前置声明
class Address;
class Person{
public :
Person(const std::string& name,const Ddate& birthday,
const Address& addr);
std::string name() const;
std::string birthdate() const;
std::string address() const;
...
private:
std::tr1::shared_ptr<PersonImpl> pImpl;//指针,指向实现物
}
在这里,main class(Person)只内含一个指针成员(这里使用tr1::shared_ptr),指向其实现类(PersonImpl)。这样的设计常被称为pimpl idiom(pimpl是"pointer to implementation"的缩写)。这种classes内的指针名称往往就是pImpl,就像上面代码那样。
这样的设计之下,person的客户就完全与Dates,Addresses以及Persons的实现细节分离了。
注意:
1、若使用object references或object pointers可以完成任务,就不要使用objects。
2、请尽量以class声明式替换class定义式。
3、为声明式和定义式提供不同的头文件。
像Person这样使用pimpl idiom的classes,往往被称为Handle classes。但这样的classes如何真正做事呢,办法之一是将它们的所有函数转交给相应的实现类(implementation classes)并由后者完成实际工作。如下面的Person两个成员函数实现:
#include"Person.h"//
#include"PersonImpl.h"
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();
}
注意:
1、支持"编译依存性最小化"的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
2、程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及template都适用。