Item 31 减小文件之间的编译依赖
2011-04-21 10:42
423 查看
C++在接口和实现之间的分离这部分工作做的不好。像下面这样的类定义,不仅指明了接口,也包含了一部分的实现细节。这将导致对实现的依赖。实现一旦改变,将增加整个的编译时间。
要想编译Person,就必须要知道string、Date和Address的定义。这些定义都是通过下面那样的include来提供的:
可是,这样的include建立了Person对这些头文件的编译依赖。如果这三个头文件改变,Person就要重新编译(string是标准库,改变的机率不大,可以随便包含)。当然依赖Person的也要重编,于是产生了级联效应。
解决这个问题有两个办法:把Person变成句柄类(Handle Class)或接口类(Interface Class)。
先看句柄类:
可以看到,这个头文件不再依赖Date和Address的头文件了,所以可以放心地把Person给客户使用。
因为这个Person只是一个声明(而非定义),隐藏了实现细节,所以用户不会写出依赖内部实现的代码;当Person的实现改变时,使用的客户也不会再受波及。
这种方案的特征:两个头文件!
比如上面的Person类,就会有PersonFwd.h和PersonImpl.h两个。给客户用的只有PersonFwd.h。
再看接口类:
使用接口类,一般会在接口类里定义工厂函数(factory function),或叫作虚拟构造函数(virtual ctor),然后再从Person里派生出实体类(concrete class)。
使用接口类和句柄类的代价:运行时的速度会慢一点;内存会多一点。
句柄类:因为使用了impl指针,所以多了一点内存。这个指针要初始化,要为对象申请内存,要销毁。
接口类:有虚函数指针,要进行函数跳转。
无论是句柄类,还是接口类,都不能使用inline函数。以Person为例,PersonFwd.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; // implementation detail Date theBirthDate; // implementation detail Address theAddress; // implementation detail };
要想编译Person,就必须要知道string、Date和Address的定义。这些定义都是通过下面那样的include来提供的:
#include <string> #include "date.h" #include "address.h"
可是,这样的include建立了Person对这些头文件的编译依赖。如果这三个头文件改变,Person就要重新编译(string是标准库,改变的机率不大,可以随便包含)。当然依赖Person的也要重编,于是产生了级联效应。
解决这个问题有两个办法:把Person变成句柄类(Handle Class)或接口类(Interface Class)。
先看句柄类:
#include <string> // standard library components shouldn't be forward-declared #include <memory> // for tr1::shared_ptr class PersonImpl; // forward decl of Person impl. class class Date; // forward decls of classes used in class Address; // Person interface 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: // ptr to implementation; std::tr1::shared_ptr<PersonImpl> pImpl; // see Item 13 for info on }; // std::tr1::shared_ptr
可以看到,这个头文件不再依赖Date和Address的头文件了,所以可以放心地把Person给客户使用。
因为这个Person只是一个声明(而非定义),隐藏了实现细节,所以用户不会写出依赖内部实现的代码;当Person的实现改变时,使用的客户也不会再受波及。
这种方案的特征:两个头文件!
比如上面的Person类,就会有PersonFwd.h和PersonImpl.h两个。给客户用的只有PersonFwd.h。
再看接口类:
class Person { public: virtual ~Person(); virtual std::string name() const = 0; virtual std::string birthDate() const = 0; virtual std::string address() const = 0; ... static std::tr1::shared_ptr<Person> // return a tr1::shared_ptr to a new create(const std::string& name, // Person initialized with the given params; const Date& birthday, // see Item 18 for why a const Address& addr); // tr1::shared_ptr is returned };
使用接口类,一般会在接口类里定义工厂函数(factory function),或叫作虚拟构造函数(virtual ctor),然后再从Person里派生出实体类(concrete class)。
使用接口类和句柄类的代价:运行时的速度会慢一点;内存会多一点。
句柄类:因为使用了impl指针,所以多了一点内存。这个指针要初始化,要为对象申请内存,要销毁。
接口类:有虚函数指针,要进行函数跳转。
无论是句柄类,还是接口类,都不能使用inline函数。以Person为例,PersonFwd.h里是不能出现任何函数体的。
但是因为这一点点代价就不使用句柄类和接口类了吗?答案是否定的。因为要尽量减小对客户的影响。
相关文章推荐
- 读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
- Item 31:最小化文件之间的编译依赖 Effective C++笔记
- [翻译] Effective C++, 3rd Edition, Item 31: 最小化文件之间的 compilation dependencies(编译依赖)(下)
- [翻译] Effective C++, 3rd Edition, Item 31: 最小化文件之间的 compilation dependencies(编译依赖)(上)
- Effective C++ Item 31 降低文件间编译依存关系
- 减少编译时源文件之间的依赖
- 最小化文件之间的编译依赖
- 减少文件之间的编译依赖
- 最小化文件之间的编译依赖
- 编写高质量的C++代码--01 减少文件之间的编译依赖
- C++箴言:最小化文件之间的编译依赖
- Effective C++ (E3 31)笔记之降低文件间编译依赖
- C++箴言:最小化文件之间的编译依赖
- 《Effective C++》读书笔记之item31:将文件间的编译依存关系降至最低
- 最小化文件之间的 compilation dependencies(编译依赖)
- 小心得:处理两个头文件互调,死循环情况(将文件之间的编译依赖关系降至最低)
- 将文件之间的编译依赖关系降至最低
- 编译2.4.20-8版本linux内核后,头文件interrupt.h和smplock.h出现依赖循环
- ant编译打包可运行的jar文件,并包含所依赖的第三方jar包
- 让VC编译出来的程序不依赖于msvcr80.dll/msvcr90.dll/msvcr100.dll等文件