Pimpl技術——編譯期封裝
Pimpl 意思為“具體實現的指針”(Pointer to Implementation),
它通過一個私有的成員指針,將指針所指向的類的內部實現數據進行隱藏,
是隱藏實現,降低耦合性和分離接口實現的一個現代 C++ 技術,並有着“編譯防火牆(compilation firewall)”的名頭。
Pimpl技術的基本應用
其中利用了C++11的std::unique_ptr來讓Impl指針的內存更易受控制。
此外由於聲明了析構函數,導致默認的移動構造/賦值函數不能生成,若默認行為符合自己的需求,則需顯式聲明 = default
(當只在.h里,Impl是個不完整的類型,所以無法在.h類直接 = default,而是在.h聲明,在.cpp使= default)
若需要給類提供拷貝性質的函數,需要額外花點心思處理std::unique_ptr(該智能指針不支持拷貝)。
// my_class.h #pragma once #include <memory> class my_class { // ... 所有的公有/保護接口都可以放在這里 ... my_class(); ~my_class(); my_class(my_class&& v); //移動構造 my_class& operator=(my_class&& v); //移動賦值 private: class Impl; std::unique_ptr<Impl> pimpl; };
// my_class.cpp // ...include其它要依賴的頭文件... #include "my_class.h" class my_class::Impl { // 在這里定義所有私有變量和方法(換句話說是my_class類的具體實現細節內容) // 現在可以改變實現,而依賴my_class.h的其他類無需重新編譯... }; my_class::my_class():pimpl(std::make_unique<Impl>()){ // ...初始化pimpl... } my_class::~my_class() = default; my_class::my_class(my_class&& v) = default; my_class::my_class& operator=(my_class&& v) = default;
代碼示例
//View.h文件 #pragma once #include <memory> class View { public: View(); ~View(); View(View&& v); View& operator=(View&& v); void display(); private: class Impl; std::unique_ptr<Impl> pimpl; };
//View.cpp文件 #include <iostream> #include <string> #include "View.h" ///////////////////////////////////////////////////////// //下面是View::Impl的定義,也就是體現了View類的具體實現細節 class View::Impl { std::string name; public: Impl(); void printName(); }; View::Impl::Impl(){ name = "DefaultName"; } void View::Impl::printName(){ std::cout << "this is my name:" << name; } /////////////////////////////////////////////////////////// //下面是View類接口的實現 View::View():pimpl(std::make_unique<Impl>()){ } View::~View() = default; View::View(View&& v) = default; View& View::operator = (View&& v) = default; void View::display(){ pimpl->printName(); }
什么時候使用Pimpl技術?
可以看到Pimpl擁有如下優點:
-
減少依賴項(降低耦合性):其一減少原類不必要的頭文件的依賴,加速編譯;其二對Impl類進行修改,無需重新編譯原類。
-
接口和實現的分離(隱藏了類的實現):私有成員完全可以隱藏在共有接口之外,給用戶一個間接明了的使用接口,尤其適合閉源API設計。
- 可使用惰性分配技術:類的某部分實現可以寫成按需分配或者實際使用時再分配,從而節省資源。
Pimpl也擁有一些缺點:
-
每個類需要占用小小額外的指針內存。
-
每個類每次訪問具體實現時都要多一個間接指針操作的開銷,並且再使用、閱讀和調試上都可能有所不便。
可以說,在性能/內存要求不敏感(非極端底層)的領域,Pimpl技術可以有相當不錯的發揮和作用。