c++中將"介面與實現分離"的兩個重要目的就是"降低檔案間的編譯依存關係"和"隱藏物件的實現細節"。而實現這一目的的關鍵技術就是pimpl模式(pointer to implementation),也即是把乙個類所有的實現細節都"**"給另乙個類來完成,而自己只負責提供介面。而實現"pimpl模式"的關鍵又是"依賴物件的宣告(declaration)而非定義(definition)
"。那麼為什麼通過依賴物件的宣告可以實現"pimpl模式",進而實現介面與實現分離?
問題:定義乙個類 class a,這個類裡面使用了類b的物件b,然後定義了乙個類b,裡面也包含了乙個類a的物件a,就成了這樣:
編譯一下a.cpp,不通過。再編譯b.cpp,還是不通過。編譯器都被搞暈了,編譯器去編譯a.h,發現包含了b.h,就去編譯b.h。編譯b.h的時候發現包含了a.h,但是a.h已經編譯過了(其實沒有編譯完成,可能編譯器做了記錄,a.h已經被編譯了,這樣可以避免陷入死迴圈。編譯出錯總比死迴圈強點),就沒有再次編譯a.h就繼續編譯。後面發現用到了a的定義,這下好了,a的定義並沒有編譯完成,所以找不到a的定義,就編譯出錯了。
兩個檔案出現了相互包含的問題,c++是不允許的。解決辦法就是使用前置宣告而不是直接引用標頭檔案
一、前置宣告的優點
(1)減小了類的體積。
(2)提高了編譯速度,因為編譯器只需知道該類已經被定義了,而無需了解定義的細節。
使用前置宣告可以減少需要包含的標頭檔案。而當乙個標頭檔案被包含進來時,相當於引入了新的依賴。只要被依賴的這個標頭檔案有修改,**就會重新編譯。而且這種依賴性是會遞迴傳遞的,也就是當頭檔案a發生了更改時,包含了標頭檔案a的標頭檔案b和所有包含標頭檔案b的標頭檔案都會被重新編譯。所以應該適當地減少這種情況的發生。
(3)通過"前置宣告"可以實現"介面與實現分離"。我們將需要提供給客戶的類分割為兩個classes:乙個只提供介面,另乙個負責實現!
二、何時使用前置宣告和#include
首先,我們為什麼要包括標頭檔案?答案很簡單,通常是我們需要獲得某個型別的定義(definition)。那麼接下來的問題就是,在什麼情況下我們才需要型別的定義,在什麼情況下我們只需要宣告就足夠了?答案是當我們需要知道這個型別的大小或者需要知道它的函式簽名的時候,我們就需要獲得它的定義。如下,哪些需要c的定義:
情況一:必須要知道c的定義,因為a作為子類,必須要知道c的內容,才能繼承
情況二:必須要知道c的定義,因為需要根據c來確定a的大小,一般用pimpl模式 改善。
情況三和情況四:不需要知道c的定義,只需要前置宣告就可以了。引用在物理上也是乙個指標,效果和指標一樣。即便沒有c的定義,a也不會有任何問題。
情況五:不需要知道c的定義,有可能老式的編譯器需要。標準庫裡面的容器(如:list、vector、map),在包括乙個list,vector,map型別的成員變數的時候,都不需要c的定義。因為它們內部其實也是使用c的指標作為成員變數,它們的大小一開始就是固定的了,不會根據模版引數的不同而改變。
情況六:不需要知道c的定義
情況七:必須要知道c的定義,需要知道呼叫函式的簽名。
情況八:對於引用和指標情況一樣
例如:類c中有:c& cdosomething(c&);
類a中有:c& adosomething (c& c) ;
以上情況,不需要知道c的定義,但是對上面的函式任意乙個c&換成c,比如像下面的幾種示例:
類c中有:c& cdosomething (c&);
類a中有:c& adosomething (c c) ;
類c中有:c& cdosomething (c);
類a中有:c& adosomething (c& c) ;
類c中有:c cdosomething (c&);
類a中有:c& adosomething (c& c) ;
類c中有:c& cdosomething (c&);
類a中有:c adosomething (c& c) ;
那麼就必須要c的定義。無論哪一種,其實都隱式包含了乙個拷貝建構函式的呼叫,比如1中引數c由拷貝建構函式生成,3中cdosomething的返回值是乙個由拷貝建構函式生成的匿名物件。因為我們呼叫了c的拷貝建構函式,所以以上無論那種情形都需要知道c的定義。
情況九和情況十:不需要知道c的定義。
二、結論
(1)前置宣告只能作為指標或引用,不能定義類的物件,自然也就不能呼叫物件中的方法了。
(2)而且需要注意,如果將類a的成員變數b* b;改寫成b& b;的話,必須要將b在a類的建構函式中,採用初始化列表的方式初始化,否則也會出錯。
(3)盡量不要在標頭檔案中包含另外的標頭檔案。盡量在標頭檔案中使用類前置宣告程式下文中要用到的類,實在需要包含其它的標頭檔案時,可以把它放在我們的類實現檔案(cpp)中。
正常結構的c++如下:
// house.h
classcbed; //
蓋房子時:現在先不買,肯定要買床的
classchouse
;// house.cpp
#include"bed.h"
#include"house.h"//
等房子開始裝修了,要買床了
chouse::chouse(void)
: bed(*newcbed()) //
這裡對引用的賦值
chouse::chouse(cbed &bedtmp)
: bed(bedtmp)
chouse::~chouse(void)
voidchouse::gotobed()
c c 前置宣告 typedef問題
前置宣告的好處很多,比如能避免標頭檔案互相包含的衝突,比如有時我們在乙個標頭檔案中只需要另乙個標頭檔案的某個型別定義,只需要對它做一下前置宣告即可,因為為了相對較小的目的要包含進來乙個很大的標頭檔案,實在有些 不值 況且這個標頭檔案可能還要被其它很多檔案再包含的,這樣代價就更大了.所以做前置宣告即可...
前置宣告的使用
本貼為 這篇文章很大程度是受到exceptional c hurb99 書中第四章 compiler firewalls and the pimpl idiom 編譯器防火牆和pimpl慣用法 的啟發,這一章講述了減少編譯時依賴的意義和一些慣用法,其實最為常用又無任何 的是使用前置宣告來取代包括標頭...
C 的前置宣告
今天一開始看一篇幾年前翻譯的google風格規範,知道了前置宣告 可以顯著減少需要包含的標頭檔案數量 因此搜尋讀了一些文章,最後才發現現在的google風格規範是不推薦使用的。這一篇大概寫了3個小時,然後在剪貼簿被我覆蓋了,重寫花了1個多小時。前置宣告 forward declaration 是類 ...