官术网_书友最值得收藏!

1.2 pimpl慣用法

這里有一個(gè)名為CSocketClient的網(wǎng)絡(luò)通信類,定義如下:

CSocketClient 類的 public 方法提供了對(duì)外接口供第三方使用,每個(gè)函數(shù)的具體實(shí)現(xiàn)都在SocketClient.cpp中,對(duì)第三方不可見(jiàn)。對(duì)于在Windows系統(tǒng)上提供給第三方使用的庫(kù),庫(kù)作者一般需要提供.h、.lib和.dll文件給庫(kù)使用者,對(duì)于Linux系統(tǒng)則需要提供.h、.a或.so文件。

不管在哪種操作系統(tǒng)上,提供像SocketClient.h這樣的頭文件給第三方使用時(shí),庫(kù)作者大多會(huì)隱隱不安——因?yàn)镾ocketClient.h文件中CSocketClient類的大量成員變量和私有函數(shù)都暴露了這個(gè)類的太多實(shí)現(xiàn)細(xì)節(jié),很容易讓使用者看出其實(shí)現(xiàn)原理。這樣的頭文件對(duì)于一些涉及核心技術(shù)實(shí)現(xiàn)的庫(kù)和SDK,是非常敏感的。

那有沒(méi)有辦法既能保持對(duì)外接口不變,又能盡量不暴露一些關(guān)鍵的成員變量和私有函數(shù)的實(shí)現(xiàn)方法呢?有,我們可以將代碼稍微修改一下:

在以上代碼中,所有的關(guān)鍵成員變量都已經(jīng)不存在了,取而代之的是一個(gè)類型為Impl的指針成員變量m_pImpl。

具體采用什么名稱,讀者完全可以根據(jù)自己的實(shí)際情況來(lái)定,不一定非要使用“Impl”和“m_pImpl”這樣的名稱。

Impl 類現(xiàn)在對(duì)使用者完全透明,為了在 CSocketClient 類中引用 Impl 類,我們?cè)赟ocketClient.h文件中使用了一個(gè)前置聲明(以上加粗代碼行),然后就可以將原來(lái)屬于CSocketClient類的成員變量轉(zhuǎn)移到Impl類中了:

我們接著在CSocketClient構(gòu)造函數(shù)中創(chuàng)建這個(gè)m_pImpl對(duì)象,在CSocketClient析構(gòu)函數(shù)中釋放這個(gè)對(duì)象:

這樣,在 CSocketClient 類內(nèi)部,對(duì)于我們?cè)瓉?lái)直接引用的成員變量,現(xiàn)在可以使用m_pImpl->變量名來(lái)引用了。

這里僅以演示隱藏 CSocketClient 的成員變量為例,隱藏類的私有方法與隱藏成員變量的做法相同,即將原來(lái)屬于CSocketClient的方法變成Impl的方法。

需要強(qiáng)調(diào)的是,在實(shí)際開(kāi)發(fā)中,由于Impl類是CSocketClient的輔助類,沒(méi)有獨(dú)立存在的必要,所以一般會(huì)將Impl類定義成CSocketClient的內(nèi)部類。即采用如下形式:

然后在ClientSocket.cpp中定義Impl類的實(shí)現(xiàn):

現(xiàn)在CSocketClient 這個(gè)類除了保留對(duì)外的接口,其內(nèi)部實(shí)現(xiàn)用到的變量和方法基本對(duì)使用者不可見(jiàn)了。C++中對(duì)類的這種封裝方法被稱為 pimpl 慣用法,即 Pointer to Implementation(也有人認(rèn)為是Private Implementation)。

在實(shí)際開(kāi)發(fā)中,Impl類的聲明和定義既可以使用class關(guān)鍵字,也可以使用struct關(guān)鍵字。在C++中,struct類型可以用于定義成員方法,但struct所有的成員變量和方法默認(rèn)都是public的。

現(xiàn)在總結(jié)該方法的優(yōu)點(diǎn),如下所述。

◎ 核心數(shù)據(jù)成員被隱藏,不必暴露在頭文件中,對(duì)使用者透明,提高了安全性。

◎ 降低了編譯依賴,提高了編譯速度。原來(lái)頭文件中的一些私有成員變量可能是非指針、非引用類型的自定義類型,需要在當(dāng)前類的頭文件中包含這些類型的頭文件。在使用了 pimpl 慣用法以后,這些私有成員變量就被移動(dòng)到當(dāng)前類的 cpp 文件中,因此頭文件不再需要包含這些成員變量的類型頭文件,當(dāng)前頭文件變得“干凈”,其他文件在引用這個(gè)頭文件時(shí),依賴的類型變少,加快了編譯速度。

◎ 接口與實(shí)現(xiàn)分離。使用了 pimpl 慣用法之后,即使 CSocketClient 或者 Impl 類的實(shí)現(xiàn)細(xì)節(jié)發(fā)生了變化,對(duì)使用者都透明,對(duì)外的CSocketClient類聲明卻仍然可以保持不變。例如,我們可以增、刪、改 Impl 的成員變量和成員方法,而保持SocketClient.h文件的內(nèi)容不變;如果不使用pimpl慣用法,則我們做不到不改變SocketClient.h文件而增、刪、改CSocketClient類的成員。

C++11標(biāo)準(zhǔn)引入了智能指針對(duì)象,我們可以使用std::unique_ptr對(duì)象來(lái)管理上述用于隱藏具體實(shí)現(xiàn)的m_pImpl指針。可以將SocketClient.h文件修改如下:

在SocketClient.cpp中修改CSocketClient對(duì)象的構(gòu)造函數(shù)和析構(gòu)函數(shù),如果編譯器僅支持C++11標(biāo)準(zhǔn),則可以這么修改:

如果編譯器支持C++14及以上標(biāo)準(zhǔn),則可以這么修改:

由于已經(jīng)使用了智能指針來(lái)管理 m_pImpl 指向的堆內(nèi)存,所以在析構(gòu)函數(shù)中不再需要顯式地釋放堆內(nèi)存:

pimp慣用法是C/C++項(xiàng)目開(kāi)發(fā)中一種非常實(shí)用的代碼編寫(xiě)策略,建議讀者掌握它。

主站蜘蛛池模板: 开远市| 西充县| 清涧县| 盈江县| 慈溪市| 嘉善县| 鄂尔多斯市| 大名县| 通许县| 嵩明县| 乌鲁木齐县| 上栗县| 石首市| 新津县| 武山县| 昌黎县| 靖州| 阜平县| 成安县| 油尖旺区| 宜宾县| 淄博市| 哈尔滨市| 洮南市| 孟村| 屏东县| 凉山| 芒康县| 平江县| 曲阳县| 长沙县| 海兴县| 县级市| 武川县| 澄迈县| 靖边县| 逊克县| 崇州市| 河西区| 内黄县| 阜平县|