您的位置:首页 > 其它

条款31:将文件间的编译依存关系降至最低

2009-07-06 11:39 543 查看
条款31:将文件间的编译依存关系降至最低
(Minimize compilation dependencies between files.)

内容:
在你们的开发团队中,一些有经验的工程师时不时地会教导新手一些基本的编程原则,其中"将接口从实现中
分离"可能是他(她)要你必须牢记原则,因为C++并没有把它做的很好,这只能靠我们在平时的编写代码中注意这
一点了,如果你不小心违背了这一原则,可能招致的后果就是:当你轻微的修改了某个类的实现,注意不是接口的
时候,再次重新BUILD一次你的工程,Oh,My God!很多文件被重新编译和链接了,Build的时间大大超出你的预期,
而这种事情的发生,估计你当时就会只恨编译器的BUILD的速度太慢,效率太低.呵呵.避免陷入这种窘境的一种有
效的方法就是本条款要提出的内容:将文件间的编译依存关系降至最低.
现在假设你写了一个Person类,一般你会这么构思你的代码:
#include <string>
#include "date.h"
#include "address.h"
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_;
};
这样写显然在Person定义文件和其含入文件之间形成了一种编译依存关系(compilation dependency).
可能就会导致开头我们提到的使你陷入窘境的情形出现.所以这里我们采取了另外一种实现方式,即将对象
实现细则隐藏与一个指针背后.具体这样做:把Person类分割为两个类,一个只提供接口,另一个负责实现该
接口.
//person.h
#include <string>
#include <memory>
using std::string;
class Date;
class Address;
class Person{
public:
Person(const string& name,const Date& birthday,const Address& addr);
string name()const;
string birthDate()const;
string address()const;
...
private:
struct Impl;
std::tr1::shared_ptr<Impl> pImpl_;
};
//person.cpp
#include "person.h"
struct Person:Impl{
Impl(const string& name,const Date& birthday,const Address& addr)
:theName_(name),theBirthDate_(birthday),theAddress_(addr){}
string name()const{
return theName_;
}
...
string theName_;
Date theBirthDate_;
Address theAddress_;
};
Person::Person(const string& name,const Date& birthday,const Address& addr)
:pImpl_(new Impl(name,birthday,addr)){
}
string Person::name()const{
return pImpl_->name();
}
...
以上这种设计常被称为pimpl idiom("pointer to implementation"),而这种class往往被称为Handle classes
这样任何实现修改都不需要Person客户端的重新编译,此外由于客户无法看到Person的实现细目,也就不可能
写出什么"取决于那些细目"的代码.这是真正的"接口与实现的分离"!这里的关键在于以
"声明的依存性"替换"定义的依存性",那正是编译依存性最小化的本质:现实中让头文
件尽可能的自我满足,万一做不到,则让它与其他文件内的声明式相依.其他每一件事都源自这个简单的设计
策略:
(1)如果使用object references或object pointers可以完成任务,就不要使用objects.
(2)如果能够,尽量以class声明式替换class定义式.
(3)为声明式和定义式提供不同的头文件.(在模板类中常用到.)
另一个制作Handle class的办法是,令Person成为一种特殊的abstract base class称之为Interface
class.这种class只有一个virtual析构函数以及一组pure virtual函数,用来叙述整个接口.一个针对Person而
写的Interface class或许看起来这样:
//Person.h
...
using std::string;
class Date;
class Address;
class Person{
public:
virtual ~Person();
virtual string name()const = 0;
virtual string birthDate()const = 0;
virtual string address()const = 0;
...
static std::tr1::shared_ptr<Person>
create(const string& name,const Date& birthday,const Address& addr);
};
...
//person.cpp
...
class RealPerson:public Person{
public:
RealPerson(const string& name,const Date& birthday,const Address& addr);
virtual ~RealPerson(){}
string name()const;
...
private:
string name_;
Date theBirthDate_;
Address theAddress_;
};
std::tr1::shared_ptr<Person> Person::create(const string& name,
const Date& birthday,
const Address& addr){
return std::tr1::shared_ptr<Person>(new RealPerson(name,birthday,addr));
}
Handle classes和Interface classes解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性.
注意一点,两种class的实现方案带来的运行成本也是不容忽视的(由于篇幅问题,我就不具体阐述,相信你们
也能自己分析的出来).如果你应该从你的实际出发,考虑用渐近方式去使用这些技术.
请记住:
■ 支持"编译依存性最小化"的一般构想是:相依于声明式,而不要相依于定义式.基于此构想的两个
手段是Handle classes和Interface classes.
■ 程序库头文件应该以"完全且仅有的声明式"的形式存在.这种做法不论是否涉及templates都适用.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐