c++ 之类的前置声明
轉(zhuǎn)自:http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx?作者:清林,博客名:飛空靜渡
剛開(kāi)始學(xué)習(xí)c++的人都會(huì)遇到這樣的問(wèn)題:
定義一個(gè)類 class A,這個(gè)類里面使用了類B的對(duì)象b,然后定義了一個(gè)類B,里面也包含了一個(gè)類A的對(duì)象a,就成了這樣:
?
?
一編譯,就出現(xiàn)了一個(gè)互包含的問(wèn)題了,這時(shí)就有人跳出來(lái)說(shuō),這個(gè)問(wèn)題的解決辦法可以這樣,在a.h文件中聲明類B,然后使用B的指針。
?
?
然后,問(wèn)題就解決了。
但是,有人知道問(wèn)題是為什么就被解決的嗎,也就是說(shuō),加了個(gè)前置聲明為什么就解決了這樣的問(wèn)題。下面,讓我來(lái)探討一下這個(gè)前置聲明。
類的前置聲明是有許多的好處的。
我們使用前置聲明的一個(gè)好處是,從上面看到,當(dāng)我們?cè)陬怉使用類B的前置聲明時(shí),我們修改類B時(shí),只需要重新編譯類B,而不需要重新編譯a.h的(當(dāng)然,在真正使用類B時(shí),必須包含b.h)。
另外一個(gè)好處是減小類A的大小,上面的代碼沒(méi)有體現(xiàn),那么我們來(lái)看下:
?
?
我們看上面的代碼,類B的大小是12(在32位機(jī)子上)。
如果我們?cè)陬怉中包含的是B的對(duì)象,那么類A的大小就是12(假設(shè)沒(méi)有其它成員變量和虛函數(shù))。如果包含的是類B的指針*b變量,那么類A的大小就是4,所以這樣是可以減少類A的大小的,特別是對(duì)于在STL的容器里包含的是類的對(duì)象而不是指針的時(shí)候,這個(gè)就特別有用了。
在前置聲明時(shí),我們只能使用的就是類的指針和引用(因?yàn)橐靡彩蔷佑谥羔樀膶?shí)現(xiàn)的)。
那么,我問(wèn)你一個(gè)問(wèn)題,為什么我們前置聲明時(shí),只能使用類型的指針和引用呢?
如果你回答到:那是因?yàn)橹羔樖枪潭ù笮?#xff0c;并且可以表示任意的類型,那么可以給你80分了。為什么只有80分,因?yàn)檫€沒(méi)有完全回答到。
想要更詳細(xì)的答案,我們看下下面這個(gè)類:
?
?
我們看下上面定義的這個(gè)類A,其中_b變量和get_b()函數(shù)是新增加進(jìn)這個(gè)類的。
那么我問(wèn)你,在增加進(jìn)_b變量和get_b()成員函數(shù)后這個(gè)類發(fā)生了什么改變,思考一下再回答。
好了,我們來(lái)列舉這些改變:
第一個(gè)改變當(dāng)然是增加了_b變量和get_b()成員函數(shù);
第二個(gè)改變是這個(gè)類的大小改變了,原來(lái)是4,現(xiàn)在是8。
第三個(gè)改變是成員_a的偏移地址改變了,原來(lái)相對(duì)于類的偏移是0,現(xiàn)在是4了。
上面的改變都是我們顯式的、看得到的改變。還有一個(gè)隱藏的改變,想想是什么。。。
這個(gè)隱藏的改變是類A的默認(rèn)構(gòu)造函數(shù)和默認(rèn)拷貝構(gòu)造函數(shù)發(fā)生了改變。
由上面的改變可以看到,任何調(diào)用類A的成員變量或成員函數(shù)的行為都需要改變,因此,我們的a.h需要重新編譯。
如果我們的b.h是這樣的:
?
?
那么我們的b.h也需要重新編譯。
如果是這樣的:
?
?
那么我們的b.h就不需要重新編譯。
像我們這樣前置聲明類A:
class A;
是一種不完整的聲明,只要類B中沒(méi)有執(zhí)行需要了解類A的大小或者成員的操作,則這樣的不完整聲明允許聲明指向A的指針和引用。
而在前一個(gè)代碼中的語(yǔ)句
A a;
是需要了解A的大小的,不然是不可能知道如果給類B分配內(nèi)存大小的,因此不完整的前置聲明就不行,必須要包含a.h來(lái)獲得類A的大小,同時(shí)也要重新編譯類B。
再回到前面的問(wèn)題,使用前置聲明只允許的聲明是指針或引用的一個(gè)原因是只要這個(gè)聲明沒(méi)有執(zhí)行需要了解類A的大小或者成員的操作就可以了,所以聲明成指針或引用是沒(méi)有執(zhí)行需要了解類A的大小或者成員的操作的。
?
?
轉(zhuǎn)自:http://blog.csdn.net/rogeryi/archive/2006/12/12/1439597.aspx
這篇文章很大程度是受到Exceptional C++ (Hurb99)書(shū)中第四章 Compiler? Firewalls and the Pimpl Idiom? (編譯器防火墻和Pimpl慣用法) 的啟發(fā),這一章講述了減少編譯時(shí)依賴的意義和一些慣用法,其實(shí)最為常用又無(wú)任何副作用的是使用前置聲明來(lái)取代包括頭文件。
Item 26 的Guideline - "Never #include a header when a forward declaration will suffice"
在這里,我自己總結(jié)了可以使用前置聲明來(lái)取代包括頭文件的各種情況和給出一些示例代碼。
首先,我們?yōu)槭裁匆^文件?問(wèn)題的回答很簡(jiǎn)單,通常是我們需要獲得某個(gè)類型的定義(definition)。那么接下來(lái)的問(wèn)題就是,在什么情況下我們才需要類型的定義,在什么情況下我們只需要聲明就足夠了?問(wèn)題的回答是當(dāng)我們需要知道這個(gè)類型的大小或者需要知道它的函數(shù)簽名的時(shí)候,我們就需要獲得它的定義。
假設(shè)我們有類型A和類型C,在哪些情況下在A需要C的定義:
1,沒(méi)有任何辦法,必須要獲得C的定義,因?yàn)槲覀儽仨氁繡的成員變量,成員函數(shù)。
2,需要C的定義,因?yàn)槲覀円繡的大小來(lái)確定A的大小,但是可以使用Pimpl慣用法來(lái)改善這一點(diǎn),詳情請(qǐng)
看Hurb的Exceptional C++。
3,4,不需要,前置聲明就可以了,其實(shí)3和4是一樣的,引用在物理上也是一個(gè)指針,它的大小根據(jù)平臺(tái)不同,可能是32位也可能是64位,反正我們不需要知道C的定義就可以確定這個(gè)成員變量的大小。
5,不需要,有可能老式的編譯器需要。標(biāo)準(zhǔn)庫(kù)里面的容器像list, vector,map,
在包括一個(gè)list<C>,vector<C>,map<C, C>類型的成員變量的時(shí)候,都不需要C的定義。因?yàn)樗鼈儍?nèi)部其實(shí)也是使用C的指針作為成員變量,它們的大小一開(kāi)始就是固定的了,不會(huì)根據(jù)模版參數(shù)的不同而改變。
6,不需要,只要我們沒(méi)有使用到C。
7,需要,我們需要知道調(diào)用函數(shù)的簽名。
8,8的情況比較復(fù)雜,直接看代碼會(huì)比較清楚一些。
????????????C&?doToC2(C&?c)?{return?doToC(c);};
從上面的代碼來(lái)看,A的一個(gè)成員函數(shù)doToC2調(diào)用了另外一個(gè)成員函數(shù)doToC,但是無(wú)論是doToC2,還是doToC,它們的的參數(shù)和返回類型其實(shí)都是C的引用(換成指針,情況也一樣),引用的賦值跟指針的賦值都是一樣,無(wú)非就是整形的賦值,所以這里即不需要知道C的大小也沒(méi)有調(diào)用C的任何函數(shù),實(shí)際上這里并不需要C的定義。
但是,我們隨便把其中一個(gè)C&換成C,比如像下面的幾種示例:
??? ??? ??? ??? C&?doToC(C&);
????????????C&?doToC2(C?c)?{return?doToC(c);};
??? ?? ?? ?? ???
??? ?? ?? ?? ?? 2.
??????????? ??? C& doToC(C);
??????????? ??? C& doToC2(C& c) {return doToC(c);};
??? ?? ?? ?? ?? 3.
??? ?? ?? ?? ???C?doToC(C&);
??????????? ??? C& doToC2(C& c) {return doToC(c);};
??? ?? ?? ?? ?? 4.
??? ?? ?? ?? ?? C& doToC(C&);
??????????? ????C?doToC2(C& c) {return doToC(c);};
無(wú)論哪一種,其實(shí)都隱式包含了一個(gè)拷貝構(gòu)造函數(shù)的調(diào)用,比如1中參數(shù)c由拷貝構(gòu)造函數(shù)生成,3中doToC的返回值是一個(gè)由拷貝構(gòu)造函數(shù)生成的匿名對(duì)象。因?yàn)槲覀冋{(diào)用了C的拷貝構(gòu)造函數(shù),所以以上無(wú)論那種情形都需要知道C的定義。
9和10都一樣,我們都不需要知道C的定義,只是10的情況下,前置聲明的語(yǔ)法會(huì)稍微復(fù)雜一些。
最后給出一個(gè)完整的例子,我們可以看到在兩個(gè)不同名字空間的類型A和C,A是如何使用前置聲明來(lái)取代直接包括C的頭文件的:
A.h
總結(jié)
以上是生活随笔為你收集整理的c++ 之类的前置声明的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 内核模式和用户模式
- 下一篇: C++继承详解:共有(public)继承