C++學(xué)習(xí)筆記之pimpl用法詳解
前言
本文主要給大家介紹了關(guān)于C++中pimpl用法的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹:
C++的pImpl可以說(shuō)是最常見(jiàn)的慣用手法了,在很多的C++項(xiàng)目和C++開(kāi)發(fā)庫(kù)中都有所見(jiàn)。plmp的縮寫就是Pointer to Implementor,顧名思義就是將真正的實(shí)現(xiàn)細(xì)節(jié)的Implementor從類定義的頭文件中分離出去,公有類通過(guò)一個(gè)私有指針指向隱藏的實(shí)現(xiàn)類,是促進(jìn)接口和實(shí)現(xiàn)分離的重要機(jī)制。
在C++語(yǔ)言中,要定義某個(gè)類型的變量或者使用類型的某個(gè)成員,就必須知道這個(gè)類的完整定義,其例外情況是:如果定義這個(gè)類型的指針,或者該類型是函數(shù)的參數(shù)或者返回類型(即使是傳值類型的),那么就可以通過(guò)前置聲明引入這個(gè)類型的名字,而不需要提供暴露其完整的類型定義,從而類型的完整定義可以被隱藏在其他hpp頭文件或者cpp實(shí)現(xiàn)文件中,而這個(gè)指針也被稱為不透明指針(opaque pointer)。通常的pImp的手法是在API的頭文件中提供接口類的定義以及實(shí)現(xiàn)類的前置聲明,實(shí)現(xiàn)類的本身定義和成員函數(shù)的實(shí)現(xiàn)都隱藏在cpp文件中去,同時(shí)為了避免實(shí)現(xiàn)類的符號(hào)污染外部名字空間,實(shí)現(xiàn)類大多作為接口類的內(nèi)部嵌套類的形式。
一、pImpl手法的優(yōu)勢(shì)和目的
1.1 信息隱蔽
私有成員完全可以隱藏在共有接口之外,尤其對(duì)于閉源API的設(shè)計(jì)尤其的適合。同時(shí),很多代碼會(huì)應(yīng)用平臺(tái)依賴相關(guān)的宏控制,這些瑣碎的東西也完全可以隱藏在實(shí)現(xiàn)類當(dāng)中,給用戶一個(gè)間接明了的使用接口再好不過(guò)了。
1.2 加速編譯
這通常是用pImpl手法的最重要的收益,稱之為編譯防火墻(compilation firewall),主要是阻斷了類的實(shí)現(xiàn)和類的實(shí)現(xiàn)兩者的編譯依賴性。這樣,類用戶不需要額外include不必要的頭文件,同時(shí)實(shí)現(xiàn)類的成員可以隨意變更,而公有類的使用者不需要重新編譯。
1.3 更好的二進(jìn)制兼容性
承接上面說(shuō)的,通常對(duì)一個(gè)類的修改,會(huì)影響到類的大小、對(duì)象的表示和布局等信息,那么任何該類的用戶都需要重新編譯才行。而且即使更新的是外部不可訪問(wèn)的private部分,雖然從訪問(wèn)性來(lái)說(shuō)此時(shí)只有類成員和友元能否訪問(wèn)類的私有部分,但是由于C++的特性是名字查找先于名字查找和重載解析的(即使不可訪問(wèn)也會(huì)返回調(diào)用失敗,而不是視而不見(jiàn)),私有部分的修改也會(huì)影響到類使用者的行為,這也迫使類的使用者需要重新編譯。而對(duì)于使用pImpl手法,如果實(shí)現(xiàn)變更被限制在實(shí)現(xiàn)類中,那公有類只持有一個(gè)實(shí)現(xiàn)類的指針,所以實(shí)現(xiàn)做出重大變更的情況下,pImpl也能夠保證良好的二進(jìn)制兼容性。
因此,獨(dú)立和自由是pImpl的精髓所在。
1.4 惰性分配
實(shí)現(xiàn)類可以做到按需分配或者實(shí)際使用時(shí)候再分配,從而節(jié)省資源提高響應(yīng)。如果你意識(shí)到這點(diǎn)了,那是很不錯(cuò)的。
二、公有類和實(shí)現(xiàn)類的隔離程度
由于公有類是實(shí)現(xiàn)類的抽象,實(shí)現(xiàn)類是公有類的封裝隱藏,推薦的隔離方式是:將所有非virtual的private成員都放置到impl中去,同時(shí)將private成員函數(shù)需要調(diào)用的公有函數(shù)也放置到impl中去,virtual函數(shù)和protected的成員不應(yīng)當(dāng)放到impl中去。
protected的成員放到impl中沒(méi)有任何的意義,因?yàn)閜rotected是相對(duì)于繼承關(guān)系而生效的;同樣的,virtual成員也不應(yīng)該放到impl中去,因?yàn)関irtual函數(shù)需要被繼承鏈中的派生類去override。這里需要提到,virtual函數(shù)也可以是private的,函數(shù)的virtual和access兩者是正交毫無(wú)關(guān)聯(lián)的,即使派生類無(wú)法訪問(wèn)基類的虛函數(shù),但是派生類仍然可以override基類的虛函數(shù)!這引出了一個(gè)Template Method的設(shè)計(jì)模式。
將private函數(shù)需要調(diào)用到的public方法也放到impl中去,是為了避免下面所描述的back pointer帶來(lái)開(kāi)銷的妥協(xié)。當(dāng)然,還有一種極端的方式是除此以外將所有的public成員都丟到impl中去,那么公有類就相當(dāng)于一個(gè)接口類,進(jìn)而所有接口都需要一個(gè)wrapper進(jìn)行調(diào)用的轉(zhuǎn)發(fā),此時(shí)公有類會(huì)實(shí)現(xiàn)的比較無(wú)趣和雜亂,而且無(wú)法被繼承復(fù)用。
三、pImpl實(shí)現(xiàn)需要注意事項(xiàng)
3.1 資源管理
盡可能避免的使用原始指針來(lái)創(chuàng)建和delete釋放實(shí)現(xiàn)類對(duì)象,使用boost::scoped_ptr
或者std::unique_ptr
來(lái)管理實(shí)現(xiàn)類對(duì)象,而且如果確實(shí)需要實(shí)現(xiàn)類共享,可以使用boost::shared_ptr
來(lái)管理。同時(shí)scoped_ptr、unique_ptr實(shí)現(xiàn)上要比shared_ptr高效的多。
如果使用智能指針管理實(shí)現(xiàn)類對(duì)象的話,使用unique_ptr則需要手動(dòng)在實(shí)現(xiàn)文件中定義共有類的析構(gòu)函數(shù),這是因?yàn)殡m然unique_ptr和shared_ptr都可以在類型不完全的情況下定義其智能指針,但是unique_ptr其析構(gòu)函數(shù)則需要具有持有類型的完全定義,而shared_ptr比較智能則沒(méi)有這個(gè)限制。
3.2 拷貝語(yǔ)義
pImpl最需要關(guān)注的就是共有類的復(fù)制語(yǔ)義,因?yàn)閷?shí)現(xiàn)類是以指針的方式作為共有類的一個(gè)成員,而默認(rèn)C++生成的拷貝操作只會(huì)執(zhí)行對(duì)象的淺復(fù)制,這顯然違背了pImpl的原本意圖,除非是真的想要底層共享一個(gè)實(shí)現(xiàn)對(duì)象。針對(duì)這個(gè)問(wèn)題,解決方式有:
a. 禁止復(fù)制操作,將所有的復(fù)制操作定義為private的,或者繼承boost::noncopyable
,或者在新標(biāo)準(zhǔn)中將這些復(fù)制操作定義為delete的即刻;
b. 顯式定義復(fù)制語(yǔ)義,創(chuàng)建新的實(shí)現(xiàn)類對(duì)象,執(zhí)行深度復(fù)制操作。此處需要記住0-3-5法則哦,要么不定義拷貝、移動(dòng)操作符,要定義就需要將他們?nèi)恐匦露x。
3.3 impl對(duì)公有類的反向引用
實(shí)現(xiàn)類中的私有成員如果需要訪問(wèn)公有類的公共、保護(hù)的成員,就必須要能夠引用到公有類對(duì)象,實(shí)現(xiàn)其手段有:
a. impl持有一個(gè)對(duì)公有類對(duì)象的指針或者引用。雖然方便但是往往會(huì)有問(wèn)題:如果持有的是引用,則拷貝賦值就難以實(shí)現(xiàn),如果持有的是指針,則需要小心指針有效性的同步負(fù)擔(dān)(比如移動(dòng)操作)。
b. 推薦的方式,是impl中的這些函數(shù)都增加一個(gè)對(duì)公有類的引用或者指針,那么其調(diào)用方法類似于:
pimpl->func(this, params);
3.4 pImpl手法的缺點(diǎn):
a. 該手法需要在調(diào)用和實(shí)現(xiàn)之間插入了一個(gè)指針,公有類在訪問(wèn)私有成員的時(shí)候都需要增加mImpl->前綴的方式,使用、閱讀和調(diào)試都可能有所不便;
b. pImpl對(duì)拷貝操作比較敏感,要么你禁止拷貝操作,要么就需要自定義拷貝操作;
c. 編譯器將不再能夠捕獲const方法中對(duì)成員變量的修改,因?yàn)樗接谐蓡T變量已經(jīng)從公有類脫離到了實(shí)現(xiàn)類當(dāng)中了,公有類的const只能保護(hù)指針值本身是否改變,而不再能進(jìn)一步保護(hù)其所指向的數(shù)據(jù)。如果要達(dá)到類似的保護(hù)效果,可以使用std::experimental::propagate_const
技術(shù)。
pImpl是一個(gè)很重要、實(shí)用的編程技巧,強(qiáng)烈建議掌握之!
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
C++實(shí)現(xiàn)PyMysql的基本功能實(shí)例詳解
這篇文章主要介紹了C++實(shí)現(xiàn)PyMysql的基本功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03C語(yǔ)言實(shí)現(xiàn)班級(jí)成績(jī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)班級(jí)成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07C++11 寫一個(gè)只觸發(fā)一次槽函數(shù)的Qt connect函數(shù)
這篇文章主要為大家介紹了C++11 寫一個(gè)只觸發(fā)一次槽函數(shù)的Qt connect函數(shù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09C語(yǔ)言實(shí)現(xiàn)三子棋游戲含完整代碼
本文詳細(xì)講解了C語(yǔ)言實(shí)現(xiàn)三子棋游戲內(nèi)含完整代碼,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11C++中的動(dòng)態(tài)分派在HotSpot?VM中的應(yīng)用小結(jié)
多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問(wèn)時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,這篇文章主要介紹了C++的動(dòng)態(tài)分派在HotSpot?VM中的重要應(yīng)用,需要的朋友可以參考下2023-09-09C++實(shí)現(xiàn)十進(jìn)制數(shù)轉(zhuǎn)換為二進(jìn)制數(shù)的數(shù)學(xué)算法
這篇文章和大家分享一下我個(gè)人對(duì)十進(jìn)制數(shù)轉(zhuǎn)換為二進(jìn)制數(shù)的想法,目前暫時(shí)更新只整數(shù)十進(jìn)制的轉(zhuǎn)換,后續(xù)會(huì)更新帶有小數(shù)的進(jìn)制轉(zhuǎn)換,代碼使用c++實(shí)現(xiàn)2021-09-09C語(yǔ)言斷言函數(shù)assert()的學(xué)習(xí)筆記
在C語(yǔ)言庫(kù)函數(shù)中提供了一個(gè)輔助調(diào)試程序的小型庫(kù),它是由assert()宏組成,本文就詳細(xì)的介紹了一下如何使用,感興趣的可以了解一下2021-11-11