服裝網(wǎng)站建設(shè)進(jìn)度及實(shí)施過程百度營銷app
一、前向聲明
前向聲明或者前置聲明(forward declaration),這個(gè)在c++中用得還是比較多的。一般的框架或者庫中,經(jīng)??梢钥吹皆谝粋€(gè)類的前面聲明了一個(gè)類,類似下面這樣:
class useclass;
class mycall{...useclass *us;
};
前向聲明,就是在應(yīng)用這個(gè)類的某個(gè)類或者區(qū)域前聲明一下,因此,被聲明的類,對編譯器來說是一個(gè)不完全的類型(incomplete type),它只是負(fù)責(zé)告訴編譯器這個(gè)類型或者名稱是有的,但沒有這里(這和extern在這一點(diǎn)上有點(diǎn)類似)實(shí)現(xiàn)。所以編譯如果遇到只是聲明這個(gè)類的指針(或引用)的情況下,允許它編譯通過。但是,在這種情況下是不允許直接操作這個(gè)類的內(nèi)部一些特征的接口(函數(shù)或者變量)。換句話說,不允許定義這個(gè)類的對象,而只能受限使用即指針或引用以及用于聲明為該類型做為形參或者返回值的函數(shù)。
它用在什么場景下?有什么用?舉一個(gè)例子,如果有兩個(gè)A和B,他們需要互相操作彼此,比如A向B寫一個(gè)數(shù),如果這個(gè)數(shù)達(dá)到一個(gè)值,就B就回寫A一個(gè)值。這時(shí)候兒怎么辦?如此A和B互相包含頭文件,就是循環(huán)引用(當(dāng)然,寫在一起或者抽象一層,好吧)。這時(shí)候兒就需要一方不包含頭文件,而使用這種前向聲明,只在cpp文件中包含對方的頭文件即可。
另外,前向聲明可以解耦。還是A和B類,如果A類是一個(gè)接口類,但接口類中主要是操作B,那么,如果把B的頭文件放出去就可以達(dá)到這個(gè)目的。但是,這樣有幾個(gè)問題,一個(gè)是可能泄露一些不想泄露的東西(特別某些算法里),另外一個(gè),B類如果需要經(jīng)常改變,那頭文件也得老跟著變。這時(shí)候兒就可以把B搞成一個(gè)指針進(jìn)行前向聲明,具體的操作只在編譯單元中進(jìn)行。
而頭文件的減少,好處還是比較多的,一個(gè)是降低了頭文件include的順序引起的莫名的問題,另外一個(gè)降低了編譯時(shí)的依賴,提高了編譯速度。
二、Pimpl
Pointer to implementation,也就是Pimpl,即通過指針指向?qū)崿F(xiàn)而不是赤裸裸的把實(shí)現(xiàn)暴露出來。在侯捷老師《c++編程規(guī)范》和《Effective Modern c++》以及大牛陳碩的《c++工程實(shí)踐經(jīng)驗(yàn)談》中都對使用PIMPL做為一種編譯防火墻提高信息的隱藏度進(jìn)行了分析說明以及各種工程實(shí)踐的總結(jié)。
Pimpl其實(shí)就是使用一個(gè)私有的指針,來指向具體的需要隱藏的實(shí)現(xiàn)(可以簡單理解為把原來接口類中的私有或保護(hù)成員抽象出來)。而具體的實(shí)現(xiàn)則通過外部接口類來操作這個(gè)指針來實(shí)現(xiàn)。由于在向外暴露的接口類頭文件中只能看到這個(gè)私有指針的前向聲明和指針聲明,外部調(diào)用人員啥也看不到。它有幾個(gè)好處:
1、隔離內(nèi)外,形成一個(gè)安全區(qū),即把錯(cuò)誤可控的設(shè)計(jì)在范圍內(nèi)
2、減少二義性的出現(xiàn)。隱藏就意味著外部調(diào)用產(chǎn)生二義性的可能性被盡量隔絕
3、前向聲明的好處,頭文件的依賴減少并帶來的編譯開銷的降低
4、對ABI有更好的兼容性
5、有可能使用延遲加載,提高資源的利用率
需要說明的是,不光c++可以使用這個(gè)技巧,C語言同樣可以。
同樣,有優(yōu)點(diǎn)就會(huì)有缺點(diǎn):
1、增加了復(fù)雜性,畢竟多一層抽象就多一層效率耗減,同時(shí)對指針的管理(new/delete)也增加了復(fù)雜性
2、需要處理拷貝(要么禁止掉)
3、const脫離了編譯器的掌控,這種況下只能通過一些輔助的手段來達(dá)到目的
三、例程
下面看一個(gè)Pimpl的例子:
//PimplExample.h
#include <memory>
class PimplExample {
public:PimplExample();~PimplExample();int GetA();int GetB(int);int GetC();private:struct Impl;Impl *pimpl_;std::unique_ptr<Impl> ptr_;// std::shared_ptr<Impl> ptr_;
};
//打開注釋,自己試試?
//PimplExample.cpp
#include <list>
#include <string>
struct PimplExample::Impl {int IGetA();int IGetB(int i) { return 0; };int d;std::list<int> l;
};PimplExample::PimplExample() : pimpl_(new Impl()) {}PimplExample::~PimplExample() { delete pimpl_; }int PimplExample::GetA()
{pimpl_->IGetA();
return 0;
}
int PimplExample::GetB(int i)
{pimpl_->IGetB(i);return 0;
}int PimplExample::Impl::IGetA()
{std::cout << "test" << std::endl;return 0;
}
//main.cpp
#include "PimplExample.h"
#include <iostream>
int main() {PimplExample mt;return 0;
}
這里面有一個(gè)小細(xì)節(jié),如果把指針換成智能指針,試著用shared_ptr和unique_ptr來完成上面的代碼,看看有什么問題沒有?在實(shí)踐中發(fā)現(xiàn)問題,解決問題,才是提升水平的一個(gè)重要手段。換成智能指針時(shí),要把顯示的析構(gòu)函數(shù)注釋掉,看看兩種指針都會(huì)有啥現(xiàn)象。
如果想把程序?qū)懙酶靡恍?#xff0c;可以看pimpl類進(jìn)一步封裝成一個(gè)單獨(dú)的類到文件中去,這樣就和實(shí)際的工程應(yīng)用相近了。
說Pimpl,其實(shí)和前向聲明是密不可分的??梢岳斫鉃镻impl是前向聲明的一個(gè)應(yīng)用場景(前向聲明是Pimpl的泛型)。理解了一個(gè)技術(shù)的本質(zhì),就可以更好的應(yīng)用這個(gè)技術(shù)并且屏蔽掉這個(gè)技術(shù)的副作用。仍然是舉這個(gè)前向聲明或者Pimpl的例子中,不是說Pimpl就包打一切,它其實(shí)是增加了復(fù)雜性,所以在非接口中,使用它就不一定是好的選擇。另外,不把整個(gè)應(yīng)用的層次搞清楚,就無法確定這個(gè)Pimpl應(yīng)用在哪一層接口上更好。
即使如此,在實(shí)際的應(yīng)用場景中,對于一些需要暴露的情況下,不一定非得把所有的成員都抽象到Pimpl的類中,包括處理虛擬函數(shù)也是如此。因此,到底如何更好的使用Pimpl需要根據(jù)實(shí)際情況來確定,不能簡單的邯鄲學(xué)步,有樣學(xué)樣。
四、總結(jié)
其實(shí)多讀書,多實(shí)踐對學(xué)計(jì)算機(jī)的人來說真得非常重要。很多人只看書,很少實(shí)踐或者干脆反過來,結(jié)果就是進(jìn)步太慢并且固步自封。同樣,多讀書,指的是多讀精品的書籍而不是什么樣的書都讀(當(dāng)然讀優(yōu)秀的代碼也可以看成一種讀書)。國內(nèi)的書籍缺點(diǎn)往往是走兩個(gè)極端,一個(gè)是學(xué)院派,只講道理,實(shí)踐很少或者干脆沒有;或者是很多一線的人員寫的書籍傾向于實(shí)戰(zhàn),理論不足。特別是理論和實(shí)踐相結(jié)合的書籍更是少之又少,這也是往往推薦初學(xué)者去學(xué)習(xí)國外經(jīng)典的原因。
這其實(shí)和國內(nèi)的環(huán)境很有關(guān)系,在整個(gè)計(jì)算機(jī)的產(chǎn)業(yè)鏈上,國內(nèi)仍然處于應(yīng)用層,偶有底層建設(shè)也大多以國外開源為基礎(chǔ),完全原生少之又少。反倒是為某個(gè)語言優(yōu)秀與否吵個(gè)沸反盈天,實(shí)屬不智。這也是大環(huán)境使然,隨著技術(shù)的發(fā)展,也許過一些年,這些東西就會(huì)補(bǔ)上來,未為可知。
Pimpl做為老生常談,已經(jīng)分析過幾次了,這里再次補(bǔ)一篇!