高階 關於巨集定義和內聯函式

2021-06-19 03:22:38 字數 4714 閱讀 6746

tips:

1. 對於單純常量,盡量用const物件或者enums替換 #define

2. 對於形似函式的巨集(marcos),最好改用inline函式替換#define

我們先來看一般的巨集定義

#define aspect_ratio 1.653;

記號名稱為aspect_ratio也許從未被編譯器看見: 也許在編譯器開始處理原始碼之前它就被預處理器取走了。於是記號aspect_ratio有可能沒有進入到幾號表(symbol table)內,於是當你運用此常量獲得乙個編譯錯誤資訊時,可能會帶來困惑,因為這個錯誤資訊也許會提到1.653而不是aspect_ratio。如果aspect_ratio被定義在乙個非你所寫的標頭檔案內,你肯定會對1.653是從何而來的感到費解。

解決之道是以乙個常量替換上述巨集:

const double aspectratio = 1.653;
作為乙個語言常量,aspectradio肯定會被編譯器看到,當然會進入記號表內。

當我們以常量替換#define,有兩種特殊情況需要注意:

第一是定義常量指標(constant pointers)。 由於常量定義通常被放在標頭檔案內(以便被不同的原始碼含入),因此有必要將指標(而不只是指標指向之物)宣告為const。即指向常量的常量指標。

第二是class專屬常量。為了將常量的作用域(scope)限制於class內,必須將它成為class的乙個成員(member);而為了確保此常量之多只有乙份實體,必須讓它成為乙個static成員:

class gameplayer;
然而你所看到的numturns的宣告式而非定義式通常c++要求你對你所使用的任何乙個東西提供乙個定義式,但如果它是乙個class的專屬常量又是static並且為整數型別(integral type,例如ints, chars, bools)則需要特殊處理只要不取它們的位址,你可以宣告並且使用他們而無需提供定義式

const int gameplayer:: numturns;// numturns的定義式,下面告訴大家為什麼沒有給予數值

請把這個式子放進乙個實現檔案而非標頭檔案,由於class常量已在宣告時獲得了初值(例如先前宣告時將其賦值為6),因此定義時 不可以 在定義初值。

順帶一提:我們無法利用#define建立乙個class的專屬常量,因為#define並不重視scope。一旦巨集被定義了,它就在其後的編譯過程中有效(除非在某處被#undef)。同時意味著#define不僅不能用來定義class專屬常量,也不能提供任何封裝性,也就是說沒有private #define之類的東西。

如果你的編譯器(錯誤地)不允許「static整數型class常量完成in class 初值設定」可改用所謂的「the enum hack」補償做法。理論基礎是「乙個屬於列舉型別(enumerated type)的數值可權充ints被使用」:

class gameplayer ;

int scores[numturns];

...}

基於數個理由enum hack值得我們認識:

第一: enum hack的行為某方面說比較像#define而不像const,有時候這正是你所想要的。例如取乙個const的位址是合法的,而取乙個enum的位址就不合法,而取乙個define的位址通常也不合法。如果你不想要乙個pointer或者reference指向你的某個整數常量,enum可以幫助你實現這個約束。

第二:實用主義。許多**用了它,所以看到它的時候你必須認識它- -! ,好吧我承認我剛認識。。。事實上,enum hack是template metaprogramming(模板元程式設計)的基礎

另一種常見的#define的誤用情況是以它實現巨集(marcos)。巨集看起來像函式,但不會招致函式呼叫(function call)帶來的額外開銷。例如:

#define call_with_max(a,b) f((a)>(b) ? (a):(b))

這般長相的巨集有太多缺點,光是想到它們就讓人苦不堪言。

無論何時你寫出這種巨集,必須記住為巨集中的所有實參加上小括號。但縱使你為所有實參都加上了小括號,看看下面意想不到的事:

int a = 5,  b = 0;

call_with_max(++a,b); //a 被累加二次

call_with_max(++a, b+10); //a 被累加一次

呼叫f之前, a的遞增次數竟然取決於 它被拿來和誰比較!!

到了template inline上場的時候了!

tips:

1. inline函式的**被放在符號表中,像巨集一樣展開,效率高

2. 類的inline函式是乙個真正的函式,檢查引數型別,確保呼叫正確

3. inline函式可作為類的成員函式,可在其中使用private和protect成員。

4. 將大多數inline限制在小型,被頻繁呼叫的函式身上,這可使得日後的除錯過程和二進位制公升級(binary upgradability)變得更容易。也可使得潛在的**膨脹問題最小化,使程式的速度提公升機會更大。

inline函式,看起來像函式,動作像函式,比巨集好的多,可以呼叫它們又不需要蒙受函式呼叫所招致的額外開銷。實際上,能夠獲取的比想到的更多,因為「免除函式呼叫成本」只是故事的一部分而已。編譯器最優化機制通常被設計用來濃縮那些「不含函式呼叫」的**,所以當你inline某個函式,或許編譯器就因此有能力為它執行語境相關最優化,大部分編譯器絕不會對著乙個「outline函式呼叫」動作執行如此之最優化。

inline函式當然也有缺點。inline函式背後的整體觀念是將「對此函式的每一次呼叫」都以函式本體替換之。這樣做的最直接結果就是增加目標碼(object code)大小。在一台記憶體有限的機器上,過渡熱衷inlining會造成程式體積太大(對可用空間而言)。即時擁有虛擬記憶體,inline造成的**膨脹也會導致額外的換頁行為(paging),降低指令快取記憶體裝置的擊中率(instruction cache hit rate),以及伴隨這些而來的效率損失。

remember: inline只是對編譯器的乙個申請,不是強制命令,這項申請可以隱喻提出,也可以明確提出。

隱喻方式是將函式定義於class定義式內:

class person // 乙個隱喻的inline申請,age被定義域class定義式內;

...private:

int theage;

};

這樣的函式通常是成員函式,friend函式也可被定義於class內,如果真是這樣, friend函式也是被隱喻宣告為inline

明確宣告inline函式的方式是在函式定義式前面加上關鍵字inline。標準的max temple是這樣實現的:

templateinline const t& std::max(const t&a, const t&b);

class derived: public: base //derived建構函式是空的,really?

private:

std::string dm1, dm2, dm3;// derived 成員1,2和3

};

derived類的建構函式是空的,看起來像是個inlining的絕佳候選人,因為它根本不含任何**,但是。。。

c++對於「物件被構建和銷毀時發生什麼事情」做了各式各樣的保證。當你使用new,動態建立的物件被其建構函式自動初始化;當你使用delete,對應的析構函式會被呼叫。當你建立乙個物件,其每乙個base class及每乙個成員變數都會被自動構造;當你銷毀乙個物件,反向程式的析構行為也會自動發生。如果有個異常在物件構造期間被丟擲,該物件已經構建好的那一部分會自動銷毀。在這些情況下c++描述了什麼一定會發生,但沒有描述是如何發生的。「事情如何發生」是編譯器的權責,不過有一點很清楚,就是它不可能憑空發生。程式內的一定有某些**讓這些事情發生,而那些**---由編譯器於編譯期間代為產生並安插到你的程式中的**--肯定存在某個地方,有時候就放在建構函式和析構函式中。下面看一下編譯器為上面**中空的derived函式做了哪些工作:

derived::derived()		//試圖構造dm1

catch(...)

try //試圖構造dm2

catch(...)

try //試圖構造dm3

catch(...)

}

這段**不能代表編譯器真正製造出來的**,因為真正的編譯器會更精確複雜地做法來處理異常,儘管如此,這已經能準確反映derived的空白建構函式必須提供的行為。相同的理由也適用於base建構函式,所以如果它被inline,所以替換「base構造函式呼叫」而插入的**都會被插入到「derived 構造函式呼叫」內。程式庫設計者必須評估「將函式宣告為inline」的衝擊:inline函式無法隨著程式庫的公升級而公升級。換句話說如果f是程式庫內的乙個inline函式,客戶將「f函式本體」編進其程式中,一旦程式設計者決定改變f,所有用到f的客戶端程式都需要重新編譯。而如果f是non-inline函式,一旦它有任何改動,客戶端只需要重新連線就好,遠比重新編譯的負擔少很多。如果程式庫採用動態連線,公升級版函式甚至可以不知不覺地被應用程式吸納。

內聯函式和巨集定義

在c中,我們常用巨集定義來達到優化速度的目的,但由於巨集定義的種種缺陷 大家應該都吃過這種苦吧 在c 中引入了內聯函式。內聯函式實現了巨集的概念,任何在類內定義的函式會自動的成為內聯函式,但是也可以在類外用inline關鍵字來定義內聯函式。內聯的目的和巨集一樣是為了減少函式呼叫的開銷,但是通常我們會...

巨集定義和內聯函式

巨集定義和內聯函式,都可以減少函式的呼叫開銷,每次呼叫函式不必壓棧和開闢新的空間。使用巨集定義和內聯函式 的執行效率高。它們的區別是 1 巨集定義是預編譯器載入,而內聯函式是由編譯器載入 2 巨集定義容易產生一些錯誤,define min x x x min 1 3 得到的結果不是我們想要的16,而...

內聯函式和巨集定義

1 容易出錯,預處理器在拷貝巨集 時,常常會產生意想不到的邊際效用,容易產生二義性。在呼叫處進行展開,會出現運算子優先順序的問題 2 不可以除錯。然而內聯函式在debug版本裡面,它根本不是真正的內斂,在release中才會成為真正的內斂。3 在c 中,巨集 無法操作類的私有資料成員。原因是,預處理...