提高C++编译速度-------pimpl 模式& 桥接模式(转)
pimpl 模式(Private Implementation),我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们一般都会修改私有接口,但是这会导致包含该头文件的所有源文件都要重新编译,这会是个麻烦事儿。Pimpl机制,顾名思义,将实现私有化,力图使得头文件对改变不透明。
桥接模式(bridge)是一种结构型设计模式,它把类的具体实现细节对用户隐藏起来,以达到类之间的最小耦合关系。在具体编程实践中桥接模式也被称为pimpl或者handle/body惯用法,它可以将头文件的依赖关系降到最小,减少编译时间,而且可以不使用虚函数实现多态。
首先,我们先看看不使用这个机制的一个实现:
// MyBase.h class MyBase { public: int foo(); }; // MyDerived.h #include "MyBase.h" class MyDerived : public MyBase { public: int bar(); };
假设你现在希望在MyBase.h中加入一个新的private和protected成员函数,那么MyDerived和所有包含MyBase.h的源文件都需要重新编译。在一个大工程中,这样的修改可能导致重新编译时间的激增。你可以使用Doxygen或者SciTools看看头文件依赖。
一般来说,不在头文件中包含头文件是一个比较好的习惯,但是这也不能完全消除修改MyBase.h带来的重新编译代价。有没有一个机制可以使得对私有接口做修改时我们可以减小重新编译的代价。
在Pimpl机制中,我们使用前置声明一个Impl类,并将这个类的一个指针实例放入主类中,如下:
// MyClass.h class MyClassImpl; // forward declaration class MyClass { public: MyClass(); ~MyClass(); int foo(); private: MyClassImpl *m_pImpl; };
现在,除非我们修改MyClass的公有接口,否则这个头文件是不会被修改了。然后,我们用这个Impl类的实现来完成主类的细节实现,在主类的构造函数中,我们完成了实现类指针的实例化:
// MyClass.cpp class MyClassImpl { public: int foo() { return bar(); } int bar() { return var++; } int var; }; MyClass::MyClass() : m_pImpl(new MyClassImpl){} MyClass::~MyClass() { try { delete m_pImpl; } catch (...) {} } int MyClass::foo(){ return m_pImpl->foo(); }
Pimpl机制其实这是桥接模式的一种变种。我们可以对实现类随意的进行增删和修改,而不会导致包含MyClass.h的源代码重新编译。当然,这样做的时间开销和空间开销也是有的。
在实践中,我们常常采用内部类来完成Pimpl机制:
首先我们声明一个类sample,它仅向外界暴露了最小的细节,真正的实现在内部类impl,sample用一个shared_ptr来保存它的指针:
class sample { private: class impl; //不完整的内部类声明 shared_ptr<impl> p; //shared_ptr成员变量 public: sample(); //构造函数 void print(); //提供给外界的接口 }; 在sample的cpp中完整定义impl类和其他功能: class sample::impl //内部类的实现 { public: void print() { cout << "impl print" << endl;} }; sample::sample():p(new impl){} //构造函数初始化shared_ptr void sample::print() //调用pimpl实现print() { p->print();}
最后是桥接模式的使用,很简单:
sample s;
s.print();
桥接模式非常有用,它可以任意改变具体的实现而外界对此一无所知,也减小了源文件之间的编译依赖,使程序获得了更多的灵活性。而shared_ptr是实现它的最佳工具之一,它解决了指针的共享和引用计数问题。