函数实现不放在头文件的原因,及何时可以放头文件的情况
1?、引子
???????在平常的?C/C++?開發(fā)中,幾乎所有的人都已經(jīng)習慣了把類和函數(shù)分離放置,一個?.h?的頭文件里放聲明,對應的?.c?或者?.cpp?中放實現(xiàn)。從開始接觸,到熟練使用,幾乎已經(jīng)形成了下意識的流程。盡管這樣的做法無可厚非,而且在不少情況下是相對合理甚至必須的,但我還是要給大家介紹?一下把實現(xiàn)全部放置到頭文件中的方式,給出可供大家使用的另一個選擇。同時針對這一做法,也順便說一下其優(yōu)缺點以及需要注意的情況。
???????我是一個很喜歡簡潔的人,多年以來甚至養(yǎng)成了這樣的癖好,如果一個功能是能夠用一條語句實現(xiàn)的,那就不要用兩條語句。在我看來,如果給別人提供一份可以復?用的代碼的話,最優(yōu)雅的狀態(tài)莫過于僅僅提供一個頭文件就全部搞定。之所以不太喜歡引入源文件,最重要的原因是源文件往往會帶來工程文件的變化;而且,在使?用過程中也會增加一些額外的操作,例如,在一個組織良好的工程里,頭文件和源文件很有可能是位于不同的目錄,這樣就會多帶來一次文件復制操作。
2?、正文
?????2.1?顧慮
?????????我遇到有不少人不使用頭文件來包含實現(xiàn),往往是出于以下幾種顧慮:
?????????1、?暴露了實現(xiàn)細節(jié)
?????????2、?頭文件被包含到不同的源文件中,會導致鏈接沖突
?????????3、?頭文件被包含到不同的源文件中,會導致有多份實現(xiàn)被編譯出來,增大可執(zhí)行體的體積
???????如果有顧慮?1?,那很顯然應該在第一時間拋棄完全在頭文件中實現(xiàn)的念頭。不過我遇到的情形里,通常后兩種顧慮占據(jù)了絕對的比例。而這種顧慮,通常是由于對?C/C++?沒有足夠的了解導致的。
??????有顧慮?2?的,經(jīng)常會是一些有?C?語言開發(fā)經(jīng)驗的程序員。他們所擔心的也往往是出現(xiàn)的全局函數(shù)的情況。例如有以下頭文件?c_function.h?(清晰起見,防衛(wèi)宏之類的代碼沒有列出):
[cpp]
int?integer_add(const?int?a,?const?int?b)?
{?
?????????return?a?+?b;?
}?
??????如果在同一工程中,有?a.c?(或者是?.cpp?)和?b.c?兩個(或兩個以上)源文件包含了此頭文件,則在鏈接時期就會發(fā)生沖突,因為在兩個源文件編譯得到的目標文件中都有一份?integer_add?的函數(shù)實現(xiàn),導致鏈接器不知道對于調(diào)用了此函數(shù)的調(diào)用者,應該使用哪一個副本。
???????2.2?著手
???????解決的辦法有兩個,各自為兩個關(guān)鍵字,一個是?inline?,另一個是?static?。使用這兩個關(guān)鍵字的任意一個來修飾?integer_add?函數(shù),都會消除上述的沖突問題,然而本質(zhì)卻大不相同。
???????如果使用?inline?,則意味著編譯器會在調(diào)用此函數(shù)的地方把函數(shù)的目標代碼直接插入,而不是放置一個真正的函數(shù)調(diào)用,實際作用就是這個函數(shù)事實上已經(jīng)不再存在,而是像宏一樣?被就地展開了。使用?inline?的副作用,首先在于毋庸置疑地,代碼的體積變大了;其次則是,這個關(guān)鍵字嚴格算起來并不是?C?語言的關(guān)鍵字,使用它多少會帶來一些移植性方面的風險,盡管主流的?C?語言編譯器都可以支持?inline?。對于?GCC?,?inline?功能關(guān)鍵字就是?inline?本身,而對于微軟的編譯器,應該是?__inline?(注意有兩個前導下劃線)。而且,根據(jù)慣例,?inline?通常都是對編譯器的某種暗示而非強制要求,編譯器有權(quán)力在你不知情的情況下把它實現(xiàn)為非?inline?的狀態(tài)(可能的原因有,函數(shù)太大或者復雜度過高)。這樣的后果是什么,不好意思,我沒有測試過。
???????如果是使用?static?,那么至少結(jié)果是可預料的。所有包含此頭文件的源文件中都會存在此函數(shù)的一份副本。雖然代碼也有一定程度的膨脹,但好就好在互相不沖突,因為?static?關(guān)鍵字保證了該函數(shù)的可見度為單個源文件之內(nèi)。
以上的討論雖然看起來主要聚焦在?C?語言上,但由于?C++?是?C?語言的超集,并且在這些方面并沒有做太多的修改,因此討論結(jié)果同樣也適用于?C++?。
????????2.3?繼續(xù)
????????對于?C?語言來講,上面的改進幾乎已經(jīng)走到了盡頭,沒有繼續(xù)發(fā)展的余地。然而對于?C++?則不同,我們還可以進一步把它做得更漂亮。
首先,我們做以下的改動:
[cpp]?
class?Integer?
{?
public:?
?????????int?add(int?a,?int?b)?
?????????{?
???????????????????return?a?+?b;?
?????????}?
};?
???????這樣的形式,幾乎連?C++?的初學者都能看出來,確實不會再發(fā)生鏈接沖突的問題了。不過也有一個問題,我們?nèi)绻嬎銉蓚€整數(shù)的和的話,需要這樣寫:
???????Integer?op;
???????op.add(i,?j);
???????而這顯然不是一種可接受的狀態(tài),之前很簡單的一條函數(shù)語句的調(diào)用,現(xiàn)在卻必須定義一個類的對象實例。于是我們再次求助于?static?(?inline?是不適用的,因為它不能去掉定義對象實例這一步,而且事實上,把實現(xiàn)寫到類定義之內(nèi)的函數(shù)缺省就是?inline?的)。現(xiàn)在,類就像這個樣子:
[cpp]
class?Integer?
{?
public:?
?????????static?int?add(int?a,?int?b)?
?????????{?
???????????????????return?a?+?b;?
?????????}?
};?
???????調(diào)用方式也相應地簡化為:
???????Integer::add(i,?j);
???????尤其需要注意的就是這里,?C++?類中的?static?函數(shù)和全局?static?函數(shù)的行為是有差異的,它編譯之后僅產(chǎn)生一份實現(xiàn)代碼,并不會由于被多個源文件包含而產(chǎn)生多份副本?。
???????這距離我們的終極目標已經(jīng)不遠了(我們的終極目標是:?add(i,?j)?就可以搞定)。于是我們再次高舉起宏這桿大旗,在頭文件里添加以下定義:
???????#define?integer_add?????????Integer::add(后注:突然想到,似乎定義?const?函數(shù)指針也可以達到相同的目的)
??????上面解決的其實僅僅是?C++?中全局函數(shù)的頭文件復用問題,那么類呢?類的情況要復雜一些。如果是?static?方法,那么正好是和上述我們對全局函數(shù)的變通實現(xiàn)是一致的;如果是?inline?的方法(不管有沒有?inline?關(guān)鍵字),則其狀態(tài)幾乎理論上等同于前面所述的?inline?全局函數(shù)的情況。那么還有最后的一種情況,?virtual函數(shù)。對于?virtual?函數(shù),我們等到的是一個好消息:它總是生成一份代碼(甚至你顯式使用?inline?關(guān)鍵字修飾)?。這里面有個玄機:?virtual?函數(shù)的地址會被寫到類的?v-table?里,是要能夠在運行期被調(diào)用的(其核心在于,其調(diào)用者以及調(diào)用時機在編譯時是不明確的),所以絕對不能生成為全部就地展開的形式。以此可以做一個推論:所?有會被求址的成員函數(shù),都會生成一份函數(shù)實體,而不能單純地去符合內(nèi)聯(lián)的修飾關(guān)鍵字。
3?、后記
??????當然,把實現(xiàn)全部放在頭文件中并不是萬金油,不是放之四海而皆準的準則,正如本文開頭所說,這僅僅是一種選擇,只不過你之前沒有想到過可以這么做,而現(xiàn)在知道了。它最適合的場合是一些規(guī)模較小的工具類的實現(xiàn)。
?
總結(jié)
以上是生活随笔為你收集整理的函数实现不放在头文件的原因,及何时可以放头文件的情况的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在类模板的声明和定义中把.h与.cpp分
- 下一篇: #pragma预处理命令