编译防火墙(一)
编译防火墙(一)
——最小编译期依赖
本文参考Exceptional C++中的Compiler Firewalls and the Pimpl Idiom章节完成,大多数程序员使用#include包含头文件的时候比实际需要的多,你也是这样吗?若是的话你可以接下来看看下面的叙述。
大多数程序员使用#include包含头文件的时候比实际需要的多,这样会严重影响并严重程序的构建时间(build timer),特别是当一个频繁使用的头文件包含了很多其他头文件的时候问题就越发严重!
讨论下面的代码,哪些#include语句是可以在不影响代码编译或者不带来副作用的情况下直接去掉的?其次,又有哪些#include语句是可以在修改适当的代码后可以去掉的?怎样修改?修改的前提是不能修改X、Y公共接口,也就是不影响到调用它的代码。
// x.h: original header
//
#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or E are templates.
// Only A and C have virtual functions.
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
#include "e.h" // class E
class X : public A, private B
{
public:
X( const C& );
B f( int, char* );
C f( int, C );
C& g( B );
E h( E );
virtual std::ostream& print( std::ostream& ) const;
private:
std::list<C> clist_;
D d_;
};
inline std::ostream& operator<<( std::ostream& os, const X& x )
{
return x.print(os);
}
首先,我们先来查看可以直接去掉的#include语句(头文件):
1. iostream 程序中尽管用到了流(stream)但是重来都没有使用过iostream,所以可以把它直接去掉而不影响程序的编译。
2. ostream 因为在程序中若是不需要知道该类型的大小,没有调用该类型的任何成员我们可以前置声明这个类型而不需要看到该类型的定义是可以的,在本程序中ostream只在参数列表和返回值类型出现过则我们只要前置声明该类型即可。
在若干年前我们是可以使用“class ostream;”这样的语句来替代“#include <ostream>”语句的,因为ostream在那个时候还是一个class类型且没有定义在std命名空间。但是现在就不行了,最起码有两个原因阻止了我们这样做:
1) ostream现在是定义在std命名空间的,c++标准规定程序员是不允许在std命名空间内声明任何东西。(但是可以特化,参考本人编写的《c++异常安全之swap》)
2) ostream 是一个模板的typedef,具体而言就是basic_ostream<char>模板类型的重定义,我们前置声明basic_ostream<char>不仅显得较为凌乱,想想,难道我们也要在我们的文件中写上一个 typedef basic_ostream<char> ostream;? 而且我们也很难前置声明basic_ostream<char>模板所依赖的类型,因为不同的stl库实现者可能对basic_ostream模板添加不同的附加模板参数。
还好,标准库提供了一个iosfwd头文件来帮助我们,它包含了所有的流模板的前置声明和对他们类型的重定义(包括ostream)——标准的typedef类型。我们仅仅所要做的就是把#include <ostream>替换成#include <iosfwd> 。
顺便说一句,一旦你看到iosfwd,人们可能会认为同样的伎俩在其他标准库中的模板上实施,比如list和string。然而,有没有类似的“strin易做图d”或“listfwd的”标准头文件。 iosfwd头是为了给流特殊处理的向后兼容性,以避免破坏在过去的几年中为“老”nontemplated版本的iostream子系统编写的代码。
也许,这时候你看到了inline operator<< 使用到了ostream对象,这是一个合理的疑问。幸运的是,还是不用看到ostream的对象只要看到前置声明就可以了。
我们来看一下这个函数的实现:
inline std::ostream& operator<<( std::ostream& os, const X& x )
{
return x.print(os);
}
该函数用到了stream&的参数和返回值(大多数人都知道不需要定义),其中这个ostream类型的参数依次传递到另一个函数的参数(这个许多人不知道不需要定义)。因为这是我们处理的只是ostream&类型对象,就没有必要为一个完整的ostream的定义。当然,有些时候我们需要的完整定义,例如,如果我们调用ostream的任何成员函数,但我们没有做这样的事情。
所以,就像我说的,我们还可以去掉另外一个头文件。
3. 可以直接把#include "e.h"删除,而用 class e;前置声明直接代替。
E作为参数和返回值,其定义不是必须的,所以我们可以前置声明该类型就可以了。
——未完待续——
接下来是探讨伟大的pimpl手法(桥接模式,Cheshire Cat)
Pimpl手法在 接口和实现分离、可移植性、异常安全等方面的讨论参考http://blog.csdn.net/shediaoxu 文章列表中关于c++异常安全的讨论(由于要保证文章质量很多文章还没有开放,需要花时间逐渐开放)
补充:综合编程 , 安全编程 ,