《Effective C++》
条款31:将文件间的编译依存关系降至最低
假设你对C++程序的某个class实现文件做了些轻微修改。注意,修改的不是class接口,而是实现,而且只改private成分。然后重新建置这个程序,预计只花数秒就好。毕竟只有一个class被修改。当你按下“Build”按钮或键入make指令时,会大吃一惊,然后感到困窘,因为你意识到整个世界都被重新编译个连接了!那么问题出在哪里呢???
问题出在C++并没有把“将接口从实现中分离”这事做的很好。Class的定义式不只详细描述了class接口,还包括十足的实现细目。例如:
#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;//实现细目 };
其中:
#include <string> #include "date.h" #include "address.h"
由于这些头文件,使得Person定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中有任何一个被改变,或这些头文件所依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。这样的连串编译依存关系会对许多项目造成难以形容的灾难。
如下是一个解决方案的思路:
将对象实现细目隐藏于一个指针背后。把Person分割成两个classes,一个只提供接口,另一个负责实现该接口。如果负责实现的那个所谓implementation class 取名为PersonImpl,Person将定义如下:
#include <string> #include <memory> class PersonImpl;//Person实现类的前置申明 class Date;//Person接口用到的classes(Date,Address)的前置申明 class Address; 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::tr1::shared_ptr<PersonImpl> pImpl;//智能指针shared_ptr,指向实物 };
这个分离的关键在于以“申明的依存性”替换“定义的依存性”,那正是编译依存性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的申明式相依。其他每一件事都源自于这个简单的设计策略:
1.如果使用object reference或object pointers可以完成任务,就不要使用objects。
2.如果能够,尽量以class申明式替换class定义式。
3.为申明式和定义式提供不同的头文件。
Handle classes其中具体做法1是将他们的所有函数转交给相应的实现类并由后者完成实际工作。例如下面是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(); }
请注意,Person构造函数以new调用PersonImpl构造函数,以及Person::name函数内调用PersonImpl::name。这是重要的,让Person变成一个Handle class并不会改变它做的事,只会改变它做事的方法。
另一个制作Handle class的办法是,令Person成为一种特殊的abstract base class,称为interface class。这种class的目的是详细一一描述derived classes的接口,因此它通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数,用来描述整个接口。
总结:
支持“编译依存性最小化”的一般构想是:相依于申明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
程序库头文件应该以“完全且仅有申明式”的形式存在。这种做法不论是否设计template都适用。
2016-11-08 23:10:40
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。