2011-07-08 49 views
13

C++中有沒有一種方法可以聲明函數沒有副作用?考慮:C++:優化函數,無副作用

LOG("message").SetCategory(GetCategory()); 

現在假設在釋放LOG宏構建創建具有SetCategory定義爲空功能的NullLogEntry對象()。所以基本上整個表達式可以(也應該)被優化掉 - 理論上說,GetCategory()調用可能有一些副作用,所以我猜編譯器不允許把它扔掉。

另一個例子可能是其參數的函數模板特殊化忽略部分(或全部),但是編譯器是不允許保存這些論點的評價在調用點,由於可能的副作用。

我對不對?或者編譯器是否可以優化掉這些調用呢?如果沒有,有沒有辦法暗示編譯器這個函數沒有副作用,所以如果忽略返回值,那麼整個調用可以被跳過?

+0

只要函數足夠小就可以內聯,所有代碼都被優化掉的機率非常好。顯然這是一個實現細節,你必須自己驗證。 –

+0

基本上,編譯器不允許在優化時進行任何*語義*更改。刪除函數調用是一種語義變化。既然你不能明確地告訴編譯器「這個函數沒有副作用」,它不會被優化。 –

+0

@Cicada:一些C++編譯器定義了一些屬性(比如'pure'),讓你告訴編譯器該函數沒有副作用,因此理論上它可以緩存結果/優化它的調用。 –

回答

16

有這樣做的沒有標準的方法,但一些編譯器有註釋,您可以使用該效果,例如,在GCC可以使用__attribute_pure__標籤在一個函數(或者__attribute__((pure)))告訴編譯器的功能是(即沒有副作用)。即在標準C庫中廣泛使用,使得例如:

char * str = get_some_string(); 
for (int i = 0; i < strlen(str); ++i) { 
    str[i] = toupper(str[i]); 
} 

可以由編譯器優化爲:

char * str = get_some_string(); 
int __length = strlen(str); 
for (int i = 0; i < __length; ++ i) { 
    str[i] = toupper(str[i]); 
} 

的功能在string.h頭聲明爲:

extern size_t strlen (__const char *__s) 
    __THROW __attribute_pure__ __nonnull ((1)); 

__THROW是萬一沒有拋出異常,這是一個C++編譯器解析功能,__nonnull((1))告訴編譯器,第一個參數s中不應該爲空(即如果參數爲空且使用-Wnullnull標誌,則觸發警告)。

+1

這看起來很有前途。任何人都知道MSVC中類似的東西? (當然標準解決方案會更好,但是...) – imre

+0

@David:你知道編譯器是否在進行函數定義和純度時進行檢查嗎? –

+0

@imre:如果你安裝了MSVC,試着看看'strlen',它可能有一個類似的屬性定義,這是一個在標準中已知的函數的一個很好的例子,它是* pure *成爲實現者使用該屬性的好候選者。 –

4

編譯器不能優化掉不透明函數的調用。但是,如果GetCategory是內聯,並因此在調用點可見,編譯器允許,而且大多數情況下,如果它認爲它沒有副作用,會優化它拿走,但不授權這樣做。

達到你想要100%的把握,你需要換整個聲明中,將評估爲您發佈配置空語句宏什麼。

+0

如何在宏中包裝一個語句來幫助?在代碼到達編譯器時,該宏不存在。它已經擴大。 **編輯**沒關係。宏觀在生產模式中擴展爲無。 –

+0

@David,我應該更加明確。編輯。 –

+1

是的,我知道將整個事物包裝在一個宏中的選擇,但我不喜歡它作爲一個解決方案,原因有兩個:a)它有點難看,b)不知何故,我覺得即使現在我可以只是提出這兩個例子,這實際上是一個更普遍的問題,並且將功能標記爲沒有副作用的能力可以有進一步的用途。 僅優化內聯函數是不夠的;一個函數參數可能是一個非常複雜的計算結果(沒有副作用),如果忽略它,跳過整個調用仍然會很好。 – imre

3

這是具有調試模式代碼的一個已知問題。

唯一可靠的解決方案(對於函數調用)是將宏本身內包裝所有調試代碼。

例如,您也許可以使用下面的代碼來代替:

LOG("message", GetCategory()); 

然後預處理器將消滅整個聲明中釋放,你不會有任何再擔心這個問題。