雷火电竞-中国电竞赛事及体育赛事平台

歡迎來(lái)到入門(mén)教程網(wǎng)!

C語(yǔ)言

當(dāng)前位置:主頁(yè) > 軟件編程 > C語(yǔ)言 >

C++學(xué)習(xí)筆記之pimpl用法詳解

來(lái)源:本站原創(chuàng)|時(shí)間:2020-01-10|欄目:C語(yǔ)言|點(diǎn)擊:

前言

  本文主要給大家介紹了關(guān)于C++中pimpl用法的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話(huà)不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹:

  C++的pImpl可以說(shuō)是最常見(jiàn)的慣用手法了,在很多的C++項(xiàng)目和C++開(kāi)發(fā)庫(kù)中都有所見(jiàn)。plmp的縮寫(xiě)就是Pointer to Implementor,顧名思義就是將真正的實(shí)現(xiàn)細(xì)節(jié)的Implementor從類(lèi)定義的頭文件中分離出去,公有類(lèi)通過(guò)一個(gè)私有指針指向隱藏的實(shí)現(xiàn)類(lèi),是促進(jìn)接口和實(shí)現(xiàn)分離的重要機(jī)制。

  在C++語(yǔ)言中,要定義某個(gè)類(lèi)型的變量或者使用類(lèi)型的某個(gè)成員,就必須知道這個(gè)類(lèi)的完整定義,其例外情況是:如果定義這個(gè)類(lèi)型的指針,或者該類(lèi)型是函數(shù)的參數(shù)或者返回類(lèi)型(即使是傳值類(lèi)型的),那么就可以通過(guò)前置聲明引入這個(gè)類(lèi)型的名字,而不需要提供暴露其完整的類(lèi)型定義,從而類(lèi)型的完整定義可以被隱藏在其他hpp頭文件或者cpp實(shí)現(xiàn)文件中,而這個(gè)指針也被稱(chēng)為不透明指針(opaque pointer)。通常的pImp的手法是在API的頭文件中提供接口類(lèi)的定義以及實(shí)現(xiàn)類(lèi)的前置聲明,實(shí)現(xiàn)類(lèi)的本身定義和成員函數(shù)的實(shí)現(xiàn)都隱藏在cpp文件中去,同時(shí)為了避免實(shí)現(xiàn)類(lèi)的符號(hào)污染外部名字空間,實(shí)現(xiàn)類(lèi)大多作為接口類(lèi)的內(nèi)部嵌套類(lèi)的形式。

一、pImpl手法的優(yōu)勢(shì)和目的

1.1 信息隱蔽

  私有成員完全可以隱藏在共有接口之外,尤其對(duì)于閉源API的設(shè)計(jì)尤其的適合。同時(shí),很多代碼會(huì)應(yīng)用平臺(tái)依賴(lài)相關(guān)的宏控制,這些瑣碎的東西也完全可以隱藏在實(shí)現(xiàn)類(lèi)當(dāng)中,給用戶(hù)一個(gè)間接明了的使用接口再好不過(guò)了。

1.2 加速編譯

  這通常是用pImpl手法的最重要的收益,稱(chēng)之為編譯防火墻(compilation firewall),主要是阻斷了類(lèi)的實(shí)現(xiàn)和類(lèi)的實(shí)現(xiàn)兩者的編譯依賴(lài)性。這樣,類(lèi)用戶(hù)不需要額外include不必要的頭文件,同時(shí)實(shí)現(xiàn)類(lèi)的成員可以隨意變更,而公有類(lèi)的使用者不需要重新編譯。

1.3 更好的二進(jìn)制兼容性

  承接上面說(shuō)的,通常對(duì)一個(gè)類(lèi)的修改,會(huì)影響到類(lèi)的大小、對(duì)象的表示和布局等信息,那么任何該類(lèi)的用戶(hù)都需要重新編譯才行。而且即使更新的是外部不可訪(fǎng)問(wèn)的private部分,雖然從訪(fǎng)問(wèn)性來(lái)說(shuō)此時(shí)只有類(lèi)成員和友元能否訪(fǎng)問(wèn)類(lèi)的私有部分,但是由于C++的特性是名字查找先于名字查找和重載解析的(即使不可訪(fǎng)問(wèn)也會(huì)返回調(diào)用失敗,而不是視而不見(jiàn)),私有部分的修改也會(huì)影響到類(lèi)使用者的行為,這也迫使類(lèi)的使用者需要重新編譯。而對(duì)于使用pImpl手法,如果實(shí)現(xiàn)變更被限制在實(shí)現(xiàn)類(lèi)中,那公有類(lèi)只持有一個(gè)實(shí)現(xiàn)類(lèi)的指針,所以實(shí)現(xiàn)做出重大變更的情況下,pImpl也能夠保證良好的二進(jìn)制兼容性。

  因此,獨(dú)立和自由是pImpl的精髓所在。

1.4 惰性分配

  實(shí)現(xiàn)類(lèi)可以做到按需分配或者實(shí)際使用時(shí)候再分配,從而節(jié)省資源提高響應(yīng)。如果你意識(shí)到這點(diǎn)了,那是很不錯(cuò)的。

二、公有類(lèi)和實(shí)現(xiàn)類(lèi)的隔離程度

  由于公有類(lèi)是實(shí)現(xiàn)類(lèi)的抽象,實(shí)現(xiàn)類(lèi)是公有類(lèi)的封裝隱藏,推薦的隔離方式是:將所有非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ù)需要被繼承鏈中的派生類(lèi)去override。這里需要提到,virtual函數(shù)也可以是private的,函數(shù)的virtual和access兩者是正交毫無(wú)關(guān)聯(lián)的,即使派生類(lèi)無(wú)法訪(fǎng)問(wèn)基類(lèi)的虛函數(shù),但是派生類(lèi)仍然可以override基類(lèi)的虛函數(shù)!這引出了一個(gè)Template Method的設(shè)計(jì)模式。

  將private函數(shù)需要調(diào)用到的public方法也放到impl中去,是為了避免下面所描述的back pointer帶來(lái)開(kāi)銷(xiāo)的妥協(xié)。當(dāng)然,還有一種極端的方式是除此以外將所有的public成員都丟到impl中去,那么公有類(lèi)就相當(dāng)于一個(gè)接口類(lèi),進(jìn)而所有接口都需要一個(gè)wrapper進(jìn)行調(diào)用的轉(zhuǎn)發(fā),此時(shí)公有類(lèi)會(huì)實(shí)現(xiàn)的比較無(wú)趣和雜亂,而且無(wú)法被繼承復(fù)用。

三、pImpl實(shí)現(xiàn)需要注意事項(xiàng)

3.1 資源管理

  盡可能避免的使用原始指針來(lái)創(chuàng)建和delete釋放實(shí)現(xiàn)類(lèi)對(duì)象,使用boost::scoped_ptr或者std::unique_ptr來(lái)管理實(shí)現(xiàn)類(lèi)對(duì)象,而且如果確實(shí)需要實(shí)現(xiàn)類(lèi)共享,可以使用boost::shared_ptr來(lái)管理。同時(shí)scoped_ptr、unique_ptr實(shí)現(xiàn)上要比shared_ptr高效的多。

  如果使用智能指針管理實(shí)現(xiàn)類(lèi)對(duì)象的話(huà),使用unique_ptr則需要手動(dòng)在實(shí)現(xiàn)文件中定義共有類(lèi)的析構(gòu)函數(shù),這是因?yàn)殡m然unique_ptr和shared_ptr都可以在類(lèi)型不完全的情況下定義其智能指針,但是unique_ptr其析構(gòu)函數(shù)則需要具有持有類(lèi)型的完全定義,而shared_ptr比較智能則沒(méi)有這個(gè)限制。

3.2 拷貝語(yǔ)義

  pImpl最需要關(guān)注的就是共有類(lèi)的復(fù)制語(yǔ)義,因?yàn)閷?shí)現(xiàn)類(lèi)是以指針的方式作為共有類(lèi)的一個(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)類(lèi)對(duì)象,執(zhí)行深度復(fù)制操作。此處需要記住0-3-5法則哦,要么不定義拷貝、移動(dòng)操作符,要定義就需要將他們?nèi)恐匦露x。

3.3 impl對(duì)公有類(lèi)的反向引用

  實(shí)現(xiàn)類(lèi)中的私有成員如果需要訪(fǎng)問(wèn)公有類(lèi)的公共、保護(hù)的成員,就必須要能夠引用到公有類(lèi)對(duì)象,實(shí)現(xiàn)其手段有:

  a. impl持有一個(gè)對(duì)公有類(lèi)對(duì)象的指針或者引用。雖然方便但是往往會(huì)有問(wèn)題:如果持有的是引用,則拷貝賦值就難以實(shí)現(xiàn),如果持有的是指針,則需要小心指針有效性的同步負(fù)擔(dān)(比如移動(dòng)操作)。

  b. 推薦的方式,是impl中的這些函數(shù)都增加一個(gè)對(duì)公有類(lèi)的引用或者指針,那么其調(diào)用方法類(lèi)似于:

pimpl->func(this, params);

3.4 pImpl手法的缺點(diǎn):

  a. 該手法需要在調(diào)用和實(shí)現(xiàn)之間插入了一個(gè)指針,公有類(lèi)在訪(fǎng)問(wèn)私有成員的時(shí)候都需要增加mImpl->前綴的方式,使用、閱讀和調(diào)試都可能有所不便;

  b. pImpl對(duì)拷貝操作比較敏感,要么你禁止拷貝操作,要么就需要自定義拷貝操作;

  c. 編譯器將不再能夠捕獲const方法中對(duì)成員變量的修改,因?yàn)樗接谐蓡T變量已經(jīng)從公有類(lèi)脫離到了實(shí)現(xiàn)類(lèi)當(dāng)中了,公有類(lèi)的const只能保護(hù)指針值本身是否改變,而不再能進(jìn)一步保護(hù)其所指向的數(shù)據(jù)。如果要達(dá)到類(lèi)似的保護(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ì)我們的支持。

上一篇:詳解C++中十六進(jìn)制字符串轉(zhuǎn)數(shù)字(數(shù)值)

欄    目:C語(yǔ)言

下一篇:C++ 實(shí)現(xiàn)哈希表的實(shí)例

本文標(biāo)題:C++學(xué)習(xí)筆記之pimpl用法詳解

本文地址:http://www.jygsgssxh.com/a1/Cyuyan/1210.html

網(wǎng)頁(yè)制作CMS教程網(wǎng)絡(luò)編程軟件編程腳本語(yǔ)言數(shù)據(jù)庫(kù)服務(wù)器

如果侵犯了您的權(quán)利,請(qǐng)與我們聯(lián)系,我們將在24小時(shí)內(nèi)進(jìn)行處理、任何非本站因素導(dǎo)致的法律后果,本站均不負(fù)任何責(zé)任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有