轉載自:http://blog.sina.com.cn/s/blog_667102dd0100wxbi.html
一、遇到的問題
1.隱藏實現
我們在給客戶端提供接口的時候只希望能暴露它的接口,而隱藏它的實現或者算法。這時候,至少至少有兩種選擇:
(1)寫一個抽象類, 然后繼承它
(2)使用PIMPL, 把實現放到內部公有的文件里,而對外部隱藏起來
2.重新編譯
當我們有一個很大的工程的時候,我們一個底層的頭文件不希望被修改,因為這會導致包含該頭文件的所有源文件都要重新編譯。
二、什么是PIMPLl機制
1.Private Implementation
直接的字面意思就是“實現私有化”,也如我們常常聽到諸如“不要改動你的公有接口”這樣的建議,Pimpl機制,顧名思義,將實現私有化,力圖使得頭文件對改變不透明。主要作用是解開類的使用接口和實現的耦合。
2.pointer to implementation
這種說法語義上更關注代碼的實現方法,也就是一個指向實現的指針。
3.橋接模式
其實,這也是一個簡單的橋接模式
三、具體分析
1.不使用PIMPL的情況
1: //base.h
2: class Base
3: {
4: public:
5: void foo();
6: };
7:
8: //sub.h
9: #include "base.h"
10: class sub : public Base
11: {
12: public:
13: void go();
14: };
可以看到,如果有base中加了一個新的成員函數或者只要做過改動,那么它的子類sub這個文件都是重新編譯才行。在一個大工程中,這樣的修改可能導致重新編譯時間的激增。
2.一個稍好點的方法
一般來說,不在頭文件中包含頭文件是一個比較好的習慣,但是這也不能完全消除修改base.h帶來的重新編譯代價。一個稍好點的方法就是只在sub.cpp中包含base.h,但這還是要重新編譯的,只是在表現上更完美了一些。
3.使用機制的情況
我們使用前置聲明一個Impl類,並將這個類的一個指針實例放入主類中。之后我們只修改Impl類內部私有的內容。
1: //base.h
2: class Imp;
3: class Base
4: {
5: public:
6: void foo();
7: private:
8: Imp* pImp;
9: };
除非我們修改base的公有接口,否則這個頭文件是不會被修改了。然后,我們用這個Impl類的實現來完成主類的細節實現,在主類的構造函數中,我們完成了實現類指針的實例化:
1: //cpp中包含實現類的頭文件
2: #include "imp.h"
3:
4: Base::Base()
5: :pImp(new Imp)
6: {
7: }
8:
9: //調用實現類
10: Base::foo()
11: {
12: pImp->foo();
13: }
14: Base::~Base()
15: {
16: try
17: {
18: delete pImp;
19: }
20: catch (...)
21: {
22: }
23: }
24:
25: //這是真正的實現
26: Imp::foo()
27: {
28: //do...xxx
29: }
4.實踐
在實踐中,常常采用內部類來完成Pimpl機制
//一個網上的例子
1: // header
2: class fruit
3: {
4: public:
5: private:
6: class impl;
7: impl* pimpl_;
8: }
9:
10: // implementation
11: class fruit::impl
12: {
13:
14: };
15:
16: fruit::fruit()
17: {
18: pimpl_ = new impl();
19: }
四、引來的其它的問題
1.效率問題,每一個類的增加,肯定會增加開銷。
2.這種機制不一定就是最好的機制,最簡單的機制才是最好的機制
3.在構造和析構的時候,由於我們要new並且要保證delete,會引出RAII原則中的資源管理類的拷貝行為問題
所以,更好的辦法是我們在使用這個機制的時候可以使用一個比較安全的智能指針,比如scoped_ptr和shared_ptr,但shared_ptr通常更合適,因為它支持拷貝和賦值。
1: class sample
2: {
3: private:
4: class impl; //不完整的內部類聲明
5: shared_ptr<impl> p; //shared_ptr成員變量
6: public:
7: sample(); //構造函數
8: void print(); //提供給外界的接口
9: };
10:
11: //在sample的cpp中完整定義impl類和其他功能:
12: class sample::impl //內部類的實現
13: {
14: public:
15: void print()
16: {
17: cout << "impl print" << endl;
18: }
19: };
20:
21: //構造函數初始化shared_ptr void sample::print()
22: sample::sample():p(new impl){}
23: {
24: //調用pimpl實現print()
25: p->print();
26: }
直接可以用:
1: sample s;
2: s.print();
(在more effective C++ Item M14中有這個問題的討論,之后我們再做學習討論)