Guru of the Week 條款27:轉呼叫函式 (轉)

worldblog發表於2007-12-13
Guru of the Week 條款27:轉呼叫函式 (轉)[@more@]

GotW#27 轉呼叫(Forwarding Functions):namespace prefix = o ns = "urn:schemas--com::office" />

難度:3 / 10

怎樣將轉呼叫函式寫得最好?原本答案很簡單,但我們已經知道C++語言近來發生了微妙的變化。

問題

轉呼叫函式對於將任務傳遞給其它函式或時很有用,尤其當它們被設計得很高效時。

評論一下下面這個轉呼叫函式。你試圖修改它嗎?如果是的話,怎麼來?

  // file f.cpp

  #include "f.h"

  /*...*/

  bool f( X x ) {

  return g( x );

  }

(說明:本次GotW的目的之一是闡明在July [1997]於London加入到C++語言中的一個微妙改進所造成的後果。)

解答

轉呼叫函式對於將任務傳遞給其它函式或物件時很有用,尤其當它們被設計得很高效時。

關鍵點是:。

評論一下下面這個轉呼叫函式。你試圖修改它嗎?如果是的話,怎麼來?

  // file f.cpp

  #include "f.h"

  /*...*/

  bool f( X x ) {

  return g( x );

  }

有兩個主要改進可使得這個函式更高效。第一個應該總被採用,第二個需要權衡。

1.傳參時使用傳const的引用代替傳值

“這不會造成混亂嗎?”你可能會問。不,它不會,至少在這種情況下。直到最近,C++語言才規定:因為可以確保引數x除了被傳遞給g()外沒有被其它地方使用,編譯器可以將x完全掉。例如,這樣的程式碼:

  X my_x;

  f( my_x );

編譯器可以:

a)產生一個my_x的複製供f()使用(就是f()的程式碼體中的形參x),然後將這個複製傳給g();或者

b)直接將my_x傳給g()而不生成複製,因為它注意到這個額外的複製除了作g()的引數外根本沒被使用。

後者更高效,不是嗎?這是編譯器試圖作的最佳化,不是嗎?

是的,是的,但只到July 1997的London會議。在那次會議上,“限制編譯器作這種取消額外複製的最佳化”的提案得到了更多的支援。〖注1〗編譯器唯一可以取消額外複製構造的地方是“返回值最佳化”(在你的C++寶典中查詢細節吧)和“臨時物件”。

這意味著,象f這樣的轉呼叫函式,編譯器被要求產生兩份複製。既然我們(作為f的作者)知道這個額外的複製不是必須的,我們應該按照通常的辦法將x申明為const X&型的引數。

(注意:如果我們一直就是這麼做的,而不是依賴於知道編譯器被允許做些什麼,那麼,這個規則的變化不會對我們造成任何影響。這就是一個“簡單就是美”例子--儘可能避開語言的細枝末節,別耍小聰明。)

2.函式內聯

這個需要權衡。要之,預設將所有函式都實現為外聯,有選擇地將確實需要內聯以提高效率的函式實現為內聯。

當你將函式內聯時,積極面是你避免了對f函式的的額外開銷。

消極面是內聯f暴露了f的實現,並使得的程式碼依賴於此實現,當f被改變時,所有的使用者程式碼都必須被重編譯。更嚴重的是,使用者程式碼現在至少需要知道函式g()的原型,這有點噁心,因為使用者根本沒有直接呼叫函式g,原本可以根本不需要知道它的原型的(至少,從我們的例子上,是這樣的)。於是,如果g()自己發生了變化,接受其它型別的其它引數時,使用者的程式碼將變得也需要知道這些型別的申明。

內聯和非內聯都可以。必須在優缺點間進行權衡,取決於f現在是怎樣被使用的以及使用的廣泛程度,和將來可能變為怎樣被使用的以及使用的廣泛程度。

GotW給出的程式碼規範:

傳參時,用傳const的引用來代替傳值

避免函式內聯,除非profiler告訴你有這個必要(員在猜測效能瓶頸點方面是很不準的)

 

注1:這個改進是必要的,它避免了編譯器未經允許地省略複製構造時帶來問題,尤其當複製構造有副作用時。很多時候,程式碼需要計算物件的複製數目。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992727/,如需轉載,請註明出處,否則將追究法律責任。

相關文章