2016-06-09 245 views
0

我試圖弄清楚什麼時候應該使用const編寫C++代碼。是pessimization的這些所有實施例或者是有益的寫代碼這樣?:C++ - 常量參數和類變量pessimization?

實施例1:

int findVal(const int OTHER_VAL) const 
{ 
    switch(OTHER_VAL) 
    { 
    case 1: 
     return 2; 
    default: 
     return 3; 
    } 
} 

實施例2:

enum class MobType 
{ 
    COW, CHICKEN, DOG, PIG 
}; 

class BaseMob 
{ 
protected: 
    BaseMob(const MobType TYPE) : TYPE(TYPE) { } 

    const MobType TYPE; 
}; 

實施例3:

void showWorld(const World &world) 
{ 
    auto data = world.getData(); 
    for (auto &i:data) 
     i.print(); 
} 
+1

知道該變量永遠不會被修改意味着當您嘗試瞭解其周圍的代碼時,您還有一件事要跟蹤。 – chris

+2

「我試圖弄清楚何時應該在編寫C++代碼時使用const」 - - 只要有可能。 –

+1

他們中的任何一個會以何種方式成爲悲觀主義者? – molbdnilo

回答

3

不,他們不是。

const關於帶自動存儲的局部變量(包括函數參數)純粹是語法糖,幫助人類程序員爲他們的代碼設置規則。它根本沒有幫助優化器。優化編譯器從C源文件中提取必要的數據移動,並對其進行優化。他們通常不關心你是否在許多不同的事情上重複使用相同的tmp變量,或者在同一個函數中有10個不同的const tmp1 = a+10;

是的,這適用於通過值傳遞的函數參數;它們是具有自動存儲的局部變量,通過寄存器或堆棧傳遞。不,這並不意味着調用者可以認爲一個函數不會修改用於參數傳遞的堆棧內存,所以它也不會幫助優化器。 (使用相同參數進行第二次函數調用仍然需要將args重寫到堆棧(如果不是所有參數都適合寄存器),因爲arg上的const不會改變被調用函數「擁有」這一事實靜態/全局/引用變量堆棧空間和暫存空間但它希望能夠使用它。)


const確實幫助。 static const int foo = 10;可以作爲一個直接常量內聯,而不是從內存加載。 (例如add eax, 10而不是add eax, [foo])。


Using const to mark a class method as not changing any class members也可以幫助編譯器避免函數調用後重新加載類成員。 (即讓他們住在寄存器中)。這大多隻適用於編譯器無法看到函數定義的情況,否則一個好的優化編譯器可以查看被調用函數的作用並相應地進行優化。 (只要它不在Unix庫中,其中符號插入意味着它不能在編譯時看到它在動態鏈接之後看到的被調用函數。)

3

每當你邏輯上不改變一個值或一個對象,你應該使它const。從邏輯上講,我並不是說每次你在技術上都被允許,但每次在你的函數,類和代碼中都是合乎邏輯的。

一個簡單的例子可以是一個簡單的「get」函數,如例1所示,這些函數不應該修改類的狀態,因此應該標記爲常量,因爲這有助於記錄用戶的意圖,除了幫助你確保班級的不變性。

在有些情況下,有必要製作一個不可變對象,如示例2所示。我們並不是經常在C++中看到這些對象,但許多其他語言經常使用它們。如果在對象生命週期中沒有添加任何值來更改某個成員,那麼也可以使其成爲const。

傳遞const引用參數爲您提供參考的性能優勢,但同時確保源對象保持未修改,這既是用戶的良好文檔,也允許進行som優化。

在提到了所有這些原因之後,還有其他原因使用const,正如在最後一段中簡要提到的優化。當編譯器知道某些東西是不變的並且沒有被修改時,它可以啓用一些非常聰明的優化,但是出於性能方面的考慮,不要使用const

這也是爲什麼通過(例如)const_cast強制轉換(可丟棄const)可能導致一些不良行爲的原因。作爲一個例子請參閱下列:

#include <stdio.h> 

static const int foo = 10; 

int constsum(void) { 
    return foo + 5; 
} 

int main(int argc, char* argv[]) { 
    int a = constsum(); 
    int* newFoo = const_cast<int*>(&foo); 
    *newFoo = 20; 
    int b = constsum(); 
    printf("%d\n", a + b); 
    return 0; 
} 

如從這個例子中(see code running here),這可能不是產生所希望的結果,如被印刷在30代碼結果,而不是作爲或許預期40.

可以看出

在檢查生產組件,我們可以看到,爲什麼(compiled into assembly):

constsum(): 
     mov  eax, 15 
     ret 
main: 
     mov  eax, 30 
     ret 

編譯器只是內聯的價值,因爲它可以看到,他們不斷,它並不需要特別小心的是,正在使用。

因此,const正確性和const的使用是一個有價值的工具,它可以使代碼的性能和穩定性受益,而且(並且不會忘記)它有助於記錄代碼。

+0

不受歡迎的,確實沒有定義。 –

+0

您的C源與您的asm不匹配。你的godbolt鏈接有不同的來源,它只是返回值而不是將它提供給'printf'。我正在摸索着腦袋,想知道這是否是UB試圖通過指向只讀內存的指針編寫的結果......(正如我確信你知道的,如果你做了某件事實際上導致了一家商店'foo '''',它只是段錯誤,例如將'newFoo'傳遞給一個非內聯函數來存儲它,但'void store(int a){* const_cast (&foo)= a;'gcc -O3'輸出} '有趣的是:只是'ret',即使用'-Wall'也沒有警告。) –

+0

'void scan(char * p){ sscanf(p,「%d」,const_cast (&foo)); }'does generate警告。https://godbolt.org/g/d8nJRP。我仍然覺得奇怪的是,在沒有進行優化的情況下,通過'const_cast'指針存儲'foo'並沒有任何警告。除非你離開演員陣容,否則即使對於scanf功能,clang也不會發出警告。 (然後它只是警告'int *'和'const int *'之間的類型不匹配,沒有任何特定於傳遞已知地址指向只讀內存的地址)。 –