inline, const, virtual, static四個關鍵字使用時應該放在哪裏?

注意,下面說的“聲明”指的是非定義處的聲明,別跟我扯什麼“所有的定義都是聲明”什麼的。

1. inline
inline可以放在聲明處,也可以放在定義處,也可以兩個地方都放。但是inline是屬於實現部分的內容,不應該出現在接口處,即類的使用者從使用的角度不需要知道是不是inline的。所以,編程規範是inline關鍵字應該只出現在定義處,而不應該在聲明處,所以class body裏不應該出現“inline”。這一點在C++ FAQ(http://www.parashift.com/c++-faq/)[9] Inline functions中有提到。BTW,所有在class body中實現的函數都是隱含inline的,因而在class body裏實現的短函數也不需要顯式的寫上“inline”。所以可以得出結論:任何情況下都不應該在class body裏看到“inline”這個關鍵字(註釋除外)。新手也許會問,如果我在a.cc裏定義了一個inline函數fun(),在b.cc裏forward declare並使用了fun。按照編程規範,b.cc中對fun()的declaration處不標inline,那b.cc中使用fun()時,編譯器怎麼知道該把fun()內聯展開呢?答案是inline函數不能像普通函數那樣在另一文件中forward declare後調用,使用inline函數的唯一方法就是include(直接或間接)其定義,所以在定義處標inline就夠了。

定義類的時候,如果inline member function的實現較短,可以和class body放在一個.h文件裏;如果實現較長,則不應該和class body堆在一起,可以單列一個-inl.h文件存放inline函數的定義。注意:inline函數爲了能夠在編譯時展開(即被inlined),所有使用該inline函數的地方都需要它的定義。所以inline函數的定義是放在.h文件中的,而不是像非inline成員函數放在.cc/.cpp文件中。

2. const
const必須同時出現在聲明處和定義處,定義const (static) data member或const member function時都是,否則會被認爲是不同的兩個函數而可能導致編譯錯誤。一個常識:全局const變量一般是放在頭文件中的。(原因可參見《C++ Primer 4th.ed》2.4和2.9節。)關於const比較麻煩的是類中的static const數據成員,這個在static部分再詳細解釋。

3. virtual
virtual關鍵字只允許放在class body裏的聲明處。如果virtual在定義處出現會導致編譯錯誤(CE)。如果一個函數fun1在類Base1中被聲明爲virtual的,那麼Base1所有的子類以及子類的子類中的fun1都是virtual的——不管有沒有再在這些子類的fun1上標明virtual關鍵字。但是,編程規範是:要在這些子類中的fun1中也明確寫上virtual。如果不這樣做,當想確定一個類的某個函數是不是virtual時,需要一層一層的檢查它的父類中的相應函數是不是virtual——這顯然不好。

4. static
static關鍵字的用法很多,這裏只討論定義類的static成員這一應用。static關鍵字只允許放在class body裏的聲明處。如果static在定義處出現會導致CE——這一點和virtual關鍵字一樣。
Static函數成員聲明的定義需要遵循的規範和普通成員函數一樣。如果寫在class body裏,也會被隱式加上inline
static數據成員除了定義和聲明這兩個活之外,還有個initialization的事。non-const static member不允許在class body裏初始化,否則編譯錯誤。所以non-const static member的初始化要放在class body外的定義處。對於const static member(注意,代碼裏是static在const前,如static const int kMyConst;),其初始化可以在聲明處也可以在定義處,但是不能兩處都有,否則CE(duplicate initialization error)。這裏有一個問題:既然const的static data member可以在class body裏面初始化,當在class裏面初始化時,class外的definition可不可省略呢?按《C++ Primer 4th.ed》12.6.2節說是不可以的,必須還要在類的外面定義一下;但是我用g++試了一下,不寫外面的定義也可以編譯通過——class body中帶初始化的聲明此時被當成了定義。爲了規範和保險,也爲了和non-const static data member統一,編程規範是外面的定義仍然保留。另外要注意的是:在class body裏初始化const static member時,初始化式必須是編譯時可以確定的;而在定義處初始化的時候,可以是任意表達式,比如一個返回鍵盤輸入數據的函數。所以,只有在class body裏進行初始化的const static data member纔可以用在要求編譯時值確定的常量處(如switch語句的case標號,template的數值形參)。
注意:所有static函數和數據成員,無論是不是const的,在class body之外的定義都不能放在.h文件中(當然除非是inline的),否則多處include時就會出multiple definition錯誤。一般應該把它們放在定義普通成員函數的.cc/.cpp文件中。這裏的容易犯的錯誤是以爲const static data member和全局域的const變量一樣是默認file local的,而把const static data member也放在.h文件中。那是不對的!
總結一下:定義類的static成員時的編程規範是
(1) static關鍵字只放在class body裏的聲明處,而不出現在定義處;const關鍵字必須在聲明處和定義處都出現;
(2) static成員,無論是不是const的,其定義一律放在.cc/.cpp文件中,不可省略,也不可放在.h文件中;
(3) non-const static data member的initialization只允許放在class外面的定義處。const 的static data member的initialization一般也放在外面的定義處;當該初始值是編譯時可確定的,並且對用戶有意義時(即能被用戶理解,有助於用戶明白該const的含義時),或者是該data member在後面的使用要求它必須是編譯時值可確定的,那初始化就應該放在class裏的聲明處。

5. 以上幾個關鍵字的組合使用:
(1) 對於變量而言,virtual和inline沒有意義,所以只有static const的組合,該組合已經在上面詳細分析過了。提醒一下,兩個關鍵詞的位置遵照各自的規則:“static”只寫在聲明處,const兩個地方都要寫。
(2) 對於非成員函數,const和virtual沒有意義 ,而本文對static又只討論定義類的static成員這一應用,所以沒有適用的組合。
(3) 對於成員函數:
     a) 如果是static的,那就不允許再聲明爲const的。另外static也不能和virtual同時使用。 我覺得這兩點本質上都是人爲規定,而不是邏輯上的顯然。比如《C++ Primer 4th.ed》說static的const成員函數顯然沒有意義,因爲const是說不允許該成員函數修改所屬的對象(object),而static成員不屬於任何對象。但其實C++也可以規定const可以用於標明一個static函數不允許修改class的static數據成員——雖然事實上沒有這樣規定。這樣涉及static的組合只有一種——inline static。使用的時候兩個關鍵詞的位置遵照各自的規則:static只寫在聲明處,inline只寫在定義處。Inline static成員函數也是一種inline函數,所以其定義和其他inline函數一樣,應當放在.h文件中。
     b) 如果不是static的,那就可以const, virtual, inline任意組合。Const自己起自己的作用,與virtual, inline無關。所以只有inline virtual一種組合需要討論。把virtual函數定義成inline的是沒有問題的,但是這種組合比較有意思:inline virtual函數的virtual性質僅在對象的動態類型和靜態類型可能不同時(即通過基類的指針或引用調用時)纔會起作用,而其inline性質僅在對象的動態類型和靜態類型一定相同時纔會起作用,即內聯展開。所以這兩個關鍵詞雖然可以組合使用,但是從來都不會同時發揮作用。至於需要遵循的代碼規範,應該是各自遵守各自的規範,且inline性質決定其定義要放在.h文件中——不過我沒有實驗過,如果有讀者找到了相關權威資料或者有實驗結果,請告訴我。 

6. 備註:

本文主要目的是總結這些關鍵字如果要用的話該放在哪,以及相應的聲明、定義、初始化怎麼寫、寫在哪,而不是要講爲什麼要用(或者說什麼時候應該用)它們。比如virtual——理論上任意非static成員函數都可以加virtual(構造函數除外)。但是,如果你給複製操作符operator=加上了virtual,那你就走的很遠了。(走的有多遠詳見《C++ Primer 4th.ed》15.4.4節)同時,如果一個類有virtual成員,但是你沒有給該類的析構函數加上virtual,那你就死的很慘了。(死的有多慘詳見《C++ Primer 4th.ed》15.4.4節)inline到底什麼時候該用那解釋起來更是一坨——用g++編譯時遞歸函數都可以內聯展開你信不信?(具體怎麼展開詳見《般若波羅蜜心經》)關於用還是不用inline的結論基本上就是:你用了會走的很遠,你不用會死的很慘。
發佈了12 篇原創文章 · 獲贊 4 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章