標籤:style blog color 使用 檔案 資料 ar 問題
前言
你是否總因標頭檔包含衝突而苦惱?
你是否因標頭檔包含錯亂而苦惱?
你是否因封裝暴露了資料而苦惱?
你是否因經常改動實現而導致重新編譯而苦惱?
在這裡, 這些問題都不是問題, 跟隨作者, 揭秘pimpl.
本文
先來看一段例子:
有A, B 2個類, 分別由A.h, A.cpp, B.h, B.cpp檔案實現.
同時, A類中包含了B類成員, B類中包含了A類成員.
1 // A.h 2 #include "B.h" 3 class A { 4 private: 5 B b; 6 }; 7 8 // B.h 9 #include "A.h"10 class B {11 private:12 A a;13 };
你是否一眼就看出了問題!
是的, 標頭檔互相包含了, 那怎麼解決此問題?
解決方案?
// A.hclass B;class A {public: A(); ~A();private: B *pB;};// A.cpp#include "B.h"A::A(): pB(new B()){}A::~A(){ delete pB;}// B.hclass A;class B {public: B(); ~B();private: A *pA;};// B.cpp#include "A.h"B::B(): pA(new A()){}B::~B(){ delete pA;}
我們在標頭檔中互相前置聲明, 這種行為是"不要錢"的, 大夥可以盡情的用.
隨後在源檔案中, 互相包含標頭檔, 在各自的建構函式中new 出對象, 解構函式中 delete 對象.
僅此而已?
接下來才是pimpl的重度解說.
假設我們的A需要實現成員函數 AmemberFunc.
這個成員函數過程複雜, 可能需要分割成多個小函數.
因此就有了一下清單:
func1();
func2();
...
funcN();
過程中還需要一系列成員變數.
因此就有了一下清單:
type1 var1, var2 ... varN;
type2 var1, var2 ... varN;
...
typeN var1, var2 ... varN;
注意, 你需要的僅僅是使用A的AmemberFunc介面.
其他的任何東西在你眼裡都是累贅, 他們並不能讓你更清楚實現, 反而擾亂你的思緒.
當你使用一個只有一個介面的對象時, 查看其標頭檔, 發現一堆的成員清單,
如果你膽子比較大, 或許會去源檔案查看清單中的東西到底都在幹什麼.
這個時候的你已經走向歧途了.
當然, 也可能源檔案已經被編譯成了庫檔案, 你的壯志雄心不得已施展.
再次注意, 你需要的僅僅是這個介面, 至於介面的實現... 除非你的目的是把這個介面一探究竟, 否則純屬浪費時間.
廢話說了不少, 看看解決方案.
// A.hclass A {public: A(); ~A(); type memberFunc();private: class impl; impl *pimpl;};// A.cpp#include "B.h"class A::impl { type memberFunc() { ... }private: func1(); func2(); ... funcN(); type1 var1, var2 ... varN; type2 var1, var2 ... varN; ... typeN var1, var2 ... varN;};A::A(): pimpl(new impl()){}A::~A(){ delete pimpl;}type A::memberFunc(){ return pimpl->memberFunc();}
A的內部聲明了一個impl類型.
該類執行個體負責實現A的所有功能.
A只需調用impl相應的函數.
此方法隱藏了所有不需要暴露的細節.
並且避免了標頭檔的依賴. (因為實現在源檔案中, 你可以在裡麵包含任意標頭檔不必考慮衝突問題.)
增強了可讀性, 使用者不會被"細節"擾亂思緒.
尾聲
pimpl的功能遠不止這些, 你可以在pimpl中拋出異常, 在外層類中捕獲異常, 從而讓異常完全透明.
還有更多功能, 等著你去發現..
當然, pimpl的缺陷也是顯而易見的.
那就是不能內聯! 不過誰會在意呢.