1 參考
- 《effective C++》 條款31:將文件間的編譯關系降至最低
- PIMPL Idiom: http://c2.com/cgi/wiki?PimplIdiom
2 什么是PIMPL?
PIMPL是指pointer to implementation。通過使用指針的方式隱藏對象的實現細節。是實現“將文件間的編譯依存關系降至最低”的方法之一。另一個方式是通過接口實現,但其原理一樣。
PIMPL又稱作“編譯防火牆”、“笑臉貓技術”,它只在C/C++等編譯語言中起作用。
3 為什么要使用PIMPL?
3.1 理論分析
龐大的項目,修改一個文件之后,重新編譯,所有依賴該文件的文件都需要重新編譯,導致編譯時間太長。
3.2 工程實例
通過描述一個實例來證明上一小節的理論。
3.2.1 不使用PIMPL
文件間的依賴關系如圖:
有三個源文件依賴“Person.h”,實際中可以有更多個文件依賴它,為了說明意思,我源碼寫的都非常簡單,主要是為了表明文件間的依賴關系而已。
Person.h #ifndef PERSON_H_ #define PERSON_H_ struct Person { void print(); }; #endif Person.cc #include "Person.h" #include <iostream> void Person::print() { std::cout << "Person::print()" << std::endl; } PersonUser.cc #include "Person.h" main.cc
#include "Person.h"
int main() { return 0; } Makefile
# # Makefile # author: zhaokai # date: 2013-11-28 # TESTS = main all : $(TESTS) clean : rm -f $(TESTS) rm -f main.o PersonUser.o Person.o main.o: main.cc Person.h g++ -c main.cc PersonUser.o : PersonUser.cc Person.h g++ -c PersonUser.cc
Person.o: Person.cc Person.h g++ -c Person.cc $(TESTS): main.o PersonUser.o Person.o g++ -o main main.o PersonUser.o Person.o
現在我們開始修改Person.h文件:
#ifndef PERSON_H_ #define PERSON_H_ struct Person { int i; // add int i void print(); };
#endif
然后make,結果如下:
依賴Person.h的三個文件都被重新編譯了,最后鏈接生成執行文件。
3.2.2 使用PIMPL
使用PIMPL需要將Person類的實現移到PersonImpl類中,使用指針的方式將實現隱藏,相當於Person.h只是一個傀儡而已,而以前依賴它的文件依舊依賴之,文件間的依賴關系如圖:
有三個源文件依賴“Person.h”,實際中可以有更多個文件依賴它,有兩個源文件和一個頭文件依賴“PersonImpl.h”。
Person.h #ifndef PERSON_H_ #define PERSON_H_ #include <memory> struct PersonImpl; struct Person { void print(); private: std::shared_ptr<PersonImpl> pImpl; }; #endif Person.cc #include "Person.h" #include "PersonImpl.h" void Person::print() { pImpl->print(); } PersonImpl.h
#ifndef PERSONIMPL_H_ #define PERSONIMPL_H_ struct PersonImpl { void print(); }; #endif PersonImpl.cc #include "PersonImpl.h" #include <iostream> void PersonImpl::print() { std::cout << "PersonImpl::print()" << std::endl; } PersonUser.cc
#include "Person.h" main.cc
#include "Person.h" int main() { return 0; } Makefile # # Makefile # author: zhaokai # date: 2013-11-28 # TESTS = main all : $(TESTS) clean : rm -f $(TESTS) rm -f main.o PersonUser.o Person.o PersonImpl.o main.o: main.cc Person.h g++ --std=c++11 -c main.cc PersonUser.o : PersonUser.cc Person.h g++ --std=c++11 -c PersonUser.cc Person.o: Person.cc Person.h PersonImpl.h g++ --std=c++11 -c Person.cc PersonImpl.o: PersonImpl.cc PersonImpl.h g++ --std=c++11 -c PersonImpl.cc $(TESTS): main.o PersonUser.o Person.o PersonImpl.o g++ -o main main.o PersonUser.o Person.o PersonImpl.o
現在我們開始修改PersonImpl.h文件,注意這時候Person.h已經是傀儡了,如果想給Person增加屬性那應該修改PersonImpl.h文件:
#ifndef PERSONIMPL_H_ #define PERSONIMPL_H_ struct PersonImpl { int i; // add int i void print(); }; #endif
然后make,結果如下:
依賴PersonImpl.h的兩個文件都重新編譯了,而依賴於“Person.h”的文件main.cc和PersonUser.cc都沒有重新編譯。
3.2.3 對比
同樣是一件事情,為Person類增加屬性int i;兩種方法導致編譯的過程就不同,我們舉得例子比較小,如果有100個類似PersonUser這樣的文件,那么使用PIMPL,編譯時還是只有“Person.cc”和“PersonImpl”兩個文件重新編譯了;但是不使用PIMPL的話,就是“main.cc”,“Person.cc”和100個類似“PersonUser.cc”這樣的文件重新編譯,那就是102個文件。
通過上面的實例就可證明理論分析部分了。
至於如何使用PIMPL,敬請期待下一篇文章!