PImpl
與std::unique_ptr
組合
pimpl(Pointer to Implementation)
是C++
程序開發中非常常用的技巧之一,它的好處有:
- 節省程序編譯時間
- 保持程序/庫的二進制兼容性
- 隱藏實現細節
舉例一個常見的pimpl
的使用示例:
// a.h
class Impl; //前置聲明
class A
{A();~A();//...
private:Impl* m_pimpl = nullptr;
};//a.cpp
//include "impl.h"
A::A()
{m_pimpl = new Impl();
}A::~A()
{if (m_pimpl != nulllptr){delete m_pimpl;m_pimpl = nullptr;}
}
這是一個最簡單的使用pimpl
隱藏實現細節的例子,在析構函數中delete m_pimpl;
確保不會內存泄漏,
在現代C++中提倡使用智能指針,我們嘗試將以上的代碼改為智能指針的形式:
// a.h
class Impl; //前置聲明
class A
{A();//...
private:std::unique_ptr<Impl> m_pimpl = nullptr;
};//a.cpp
//include "impl.h"
A::A()
{m_pimpl = std::make_unique<Impl>();
}
可以看到,我們將A
類的析構函數刪掉了,因為智能指針會自動幫我管理Impl
申請的堆內存
但是這段代碼無法通過編譯,報錯: error: C2338: can't delete an incomplete type
;
導致這個問題產生的原因:
A類被銷毀時,類A的析構函數被調用,而類A并沒有顯示的析構函數,根據編譯器生成特殊成員函數的規則,編譯器會幫我們生成一個默認的析構函數,在這個析構函數中,調用 m_pimpl
成員變量的析構函數,這是一個使用默認刪除器
的std::unique_ptr<Impl>
類對象,在這個類的析構函數中,會檢查它所持有的類型是否為不完整類型;
由于編譯器生成的特殊成員函數是隱式內斂
的,它們的作用域僅在a.h
頭文件中,而在這個頭文件中,只有Impl
類的前置聲明,沒有Impl
類的實現,所以在這些編譯器默認生成的函數中,Impl
是一個不完整的類型
怎樣解決這個問題:
為類A
手動添加析構函數,且不要在頭文件中寫實現體,因為Impl
類的完整實現僅在a.h
中不可見,在a.cpp
文件中可見;
參考1
參考2