c++ - 使用'const'作為函數參數


你用const去多遠? 你是否只是在必要時使函數成為const ,或者你是否全力以赴地使用它? 例如,設想一個簡單的使用單個布爾參數的增變器:

void SetValue(const bool b) { my_val_ = b; }

那個const實際上有用嗎? 我個人選擇廣泛使用它,包括參數,但在這種情況下,我想知道它是否值得?

我也驚訝地發現,你可以在函數聲明中忽略const的參數,但是可以將它包含在函數定義中,例如:

.h文件

void func(int n, long l);

.cpp文件

void func(const int n, const long l)

是否有一個原因? 這對我來說似乎有點不尋常。


Answers



原因是該參數的const只適用於本地函數內,因為它正在處理數據的副本。 這意味著函數簽名實際上是相同的。 儘管這樣做很可能是不好的風格。

我個人傾向於不使用const,除了引用和指針參數。 對於復制的對象,它並不重要,但它可以更安全,因為它表示函數內的意圖。 這真是一個判斷力的呼喚。 我確實傾向於使用const_iterator,雖然在循環時我不打算修改它,所以我想每個人都可以,只要const參考類型的正確性得到嚴格維護。




“當參數按值傳遞時const是毫無意義的,因為你不會修改調用者的對象。

錯誤。

這是關於自我記錄你的代碼和你的假設。

如果你的代碼有很多人在工作,你的函數是不平凡的,那麼你應該標記“const”,你可以做任何事情。 在寫行業代碼的時候,你應該總是假設你的同事是精神病患者,試圖讓你得到任何可能的方式(特別是因為它往往是你自己的未來)。

另外,正如前面提到的那樣,它可能會幫助編譯器優化一些東西(儘管這是一個很長的一步)。




有時(太頻繁!)我必須解開別人的C ++代碼。 我們都知道別人的 C ++代碼幾乎是按照定義完成的:)所以我做的第一件事情就是在每一個變量定義中都將const放入const ,直到編譯器開始吠叫。 這也意味著const值限定的參數,因為它們只是調用者初始化的局部變量。

嗯,我希望變量默認是const ,非常量變量需要mutable




以下兩行在功能上是等同的:

int foo (int a);
int foo (const int a);

很明顯,如果第二種方法被定義的話,你將無法修改foo的內容,但是與外部沒有任何區別。

const真正派上用場的地方是使用引用或指針參數:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

這說的是,foo可以採取一個大的參數,也許是一個千兆字節大小的數據結構,而不需要復制它。 另外,它對調用者說,“Foo不會*改變該參數的內容”。 傳遞const引用也允許編譯器做出某些性能決定。

*:除非它消除了常量,但這是另一個帖子。




額外多餘的const從API的角度來看是不好的:

在代碼中添加額外多餘的const值以傳遞值為參數的內部類型參數混淆了您的API,但對調用方或API用戶沒有任何有意義的承諾(這只會妨礙實現)。

在不需要API的時候太多的“const”就像是“ 哭鬧的狼 ”,最終人們會開始忽視“const”,因為它到處都是,而且大部分時間都沒有意義。

API中額外const的“reductio ad absurdum”參數對於前兩個點是好的,如果更多的const參數是好的,那麼每個可以有const的參數都應該有一個const。 事實上,如果真的那麼好,你會希望const成為參數的默認值,並且只有當你想改變參數時才有一個像“mutable”這樣的關鍵字。

所以,讓我們盡可能地嘗試把常量放進去:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

考慮上面的代碼行。 不僅聲明更加混亂,閱讀時間越來越長,而且四個“常量”關鍵字中的三個可以被API用戶安全地忽略。 但是,額外使用'const'使第二行可能是危險的!

為什麼?

第一個參數char * const buffer的快速誤讀可能會讓你認為它不會修改傳入的數據緩衝區中的內存,但是這不是真的! 多餘的“const”可能會導致您在快速掃描或誤讀時API的危險和錯誤的假設

從代碼實現的角度來看,多餘的const也是不好的:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

如果FLEXIBLE_IMPLEMENTATION不是真的,那麼API是“有前途的”,而不是在下面的第一種方式實現該功能。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

這是一個非常愚蠢的承諾。 為什麼你應該做出一個承諾,對你的調用者沒有任何好處,只會限制你的實現?

這兩個都是相同功能的完美有效的實現,儘管如此,你所做的一切都是不必要地背在背後。

此外,這是一個很容易(和合法規避)的非常淺的承諾。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

看,我實現了這種方式無論如何,即使我答應不 - 只是使用包裝函數。 就好像這個壞人承諾不殺電影中的某個人,並命令他的同胞殺死他們。

那些多餘的const值得不過是一個電影壞人的承諾。

但是撒謊的能力變得更糟:

我已經開悟了,你可以通過使用偽常量來使頭部(聲明)和代碼(定義)中的const不匹配。 const開朗的擁護者聲稱這是一件好事,因為它可以讓你只將const放在定義中。

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

然而,相反是真實的......你可以只在聲明中放置一個偽造的const,並在定義中忽略它。 這只會使API中多餘的const成為一個可怕的謊言 - 看到這個例子:

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

所有多餘的const實際上是通過迫使他使用另一個本地拷貝或包裝函數,當他想要改變變量或通過非const引用傳遞變量時,使得實現者的代碼不太可讀。

看看這個例子。 哪個更具可讀性? 是否顯而易見,第二個函數的額外變量的唯一原因是因為一些API設計器扔了一個多餘的const?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

希望我們在這裡學到了一些東西。 多餘的const是一個API混亂的眼睛,一個討厭的嘮叨,一個淺薄的毫無意義的承諾,一個不必要的障礙,偶爾會導致非常危險的錯誤。




const應該是C ++中的默認值。 喜歡這個 :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable



當我為C ++編寫代碼時,我把所有可能的東西都放在了一邊。 使用const是幫助編譯器幫助你的好方法。 例如,強制你的方法返回值可以讓你免於錯別字,例如:

foo() = 42

當你的意思是:

foo() == 42

如果foo()被定義為返回一個非const引用:

int& foo() { /* ... */ }

編譯器會很高興讓你給函數調用返回的匿名臨時值賦值。 使其不變:

const int& foo() { /* ... */ }

消除這種可能性。




關於comp.lang.c ++的舊文章“Guru of the Week”,在這裡有一個很好的討論。

相應的GOTW文章可以在Herb Sutter的網站上找到




我使用const作為引用(或指針)的函數參數,它們只是[in]數據,不會被函數修改。 意思是,當使用引用的目的是為了避免複製數據,而不允許改變傳遞的參數。

在你的例子中,將const放在布爾b參數上只會對實現施加一個約束,並不會影響類的接口(儘管通常不會改變參數)。

的函數簽名

void foo(int a);

void foo(const int a);

是一樣的,這解釋了你的.c和.h

阿薩夫




我說const你的價值參數。

考慮這個越野車功能:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

如果數字參數是const,編譯器會停止並警告我們這個錯誤。




如果你使用->*.*運算符,這是必須的。

它阻止你寫類似的東西

void foo(Bar *p) { if (++p->*member > 0) { ... } }

我現在差不多做了,可能不會做你想要的。

我想說的是

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

如果我在Bar *p之間加了一個const ,編譯器會告訴我這個。




啊,一個艱難的。 一方面,一個聲明是一個合約,根據值傳遞一個const參數是沒有意義的。 另一方面,如果你看一下函數的實現,那麼如果你聲明一個參數常量,就給了編譯器更多的優化機會。




因為你不會修改調用者的對象,所以const通過值傳遞時是沒有意義的。

除非函數的目的是修改傳遞的值,否則在通過引用傳遞時,const應該是首選。

最後,一個不修改當前對象(this)的函數可以,也許應該聲明為const。 下面是一個例子:

int SomeClass::GetValue() const {return m_internalValue;}

這是承諾不修改應用此調用的對象。 換句話說,你可以打電話給:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

如果該函數不是const,則會導致編譯器警告。




標記值參數'const'絕對是一個主觀的東西。

不過,我更喜歡標記值參數const,就像你的例子。

void func(const int n, const long l) { /* ... */ }

對我的價值是清楚地表明函數參數值永遠不會被函數改變。 它們在開始和結束時會有相同的價值。 對我來說,這是保持一種非常實用的編程風格的一部分。

對於一個簡短的函數來說,在'const'那裡可以浪費時間/空間,因為通常這個函數並沒有修改參數。

然而,對於一個更大的函數,它是一種實現文檔的形式,它由編譯器強制執行。

我可以肯定,如果我用'n'和'l'做一些計算,我可以重構/移動這個計算,而不用擔心會得到不同的結果,因為我錯過了一個或兩個都改變了的地方。

由於它是一個實現細節,因此不需要在頭中聲明值參數const,就像不需要聲明與實現使用的名稱相同的函數參數一樣。




在你提到的情況下,它不會影響你的API的調用者,這就是為什麼它不常用(在頭文件中是不必要的)。 它只影響你的功能的執行。

這不是一件特別糟糕的事情,但是它的好處並不是很大,因為它不會影響你的API,並且增加了輸入,所以通常不會這樣做。




我不使用const值傳遞參數。 調用者不關心你是否修改參數,這是一個實現細節。

真正重要的是,如果方法不修改它們的實例,則將方法標記為常量。 這樣做,因為否則你可能會得到很多const_cast <>或者你可能會發現標記一個方法const需要改變很多的代碼,因為它調用其他方法,應該被標記為const。

如果我不需要修改它,我也傾向於標記局部變量const。 我相信通過更容易識別“移動部分”,使得代碼更容易理解。




我傾向於盡可能使用const。 (或者其他適當的目標語言關鍵字。)我這樣做純粹是因為它允許編譯器進行額外的優化,否則將無法做出。 由於我不知道這些優化可能是什麼,我總是這樣做,即使它看起來很愚蠢。

據我所知,編譯器可能會很好地看到一個常量值參數,並說:“嘿,這個函數不會修改它,所以我可以通過引用傳遞並節省一些時鐘週期。 我不認為它會做這樣的事情,因為它改變了功能簽名,但是它表明了這一點。 也許它做了一些不同的堆棧操作或什麼...重點是,我不知道,但我知道試圖比編譯器更聰明只會導致我被羞辱。

C ++有一些額外的包袱,具有const正確性的思想,所以它變得更加重要。




我使用const我可以。 參數的Const意味著它們不應該改變它們的值。 當通過引用傳遞時,這是特別有價值的。 const函數聲明函數不應該改變類成員。




可能是這不會是一個有效的論點。 但是如果我們在一個函數內增加一個const變量的值,編譯器會給我們一個錯誤:“ error:只讀參數的增量 ”。 所以這意味著我們可以使用const關鍵字來防止意外地修改函數內部的變量(我們不應該是/只讀的)。 所以如果我們在編譯時不小心做了這個,編譯器會讓我們知道。 如果你不是唯一一個從事這個項目的人,這一點特別重要。




Const參數只有在參數傳遞時才有用,例如引用或指針。 當編譯器看到一個const參數時,它確保參數中使用的變量在函數體內不被修改。 為什麼會有人想要一個按值參數保持不變? :-)




如果參數是通過值(而不是參考)傳遞的,那麼參數是否被聲明為const(除非它包含引用成員 - 對於內置類型不是問題),通常沒有太大區別。 如果參數是一個引用或指針,通常最好保護被引用/指向的內存,而不是指針本身(我認為你不能把引用本身設為const,不是那麼重要,因為你不能改變裁判) 。 保護你所能做的一切似乎是個好主意。 如果參數只是POD(包括內置類型),並且沒有機會沿著道路進一步改變(例如在您的示例中為bool參數),則可以忽略它。

我不知道.h / .cpp文件聲明的區別,但它確實是有道理的。 在機器代碼級別,沒有什麼是“const”,所以如果你聲明一個函數(在.h中)作為非const,那麼代碼就像你把它聲明為const(拋開優化)一樣。 但是,它可以幫助您使編譯器不會在函數(.ccp)的實現中更改變量的值。 當你從一個允許改變的接口繼承的情況下,它可能會派上用場,但是你不需要改變參數來實現所需的功能。







總結:

  • “通常情況下,傳遞價值是毫無用處和誤導的。” 從GOTW006
  • 但是,您可以像在變量中一樣將它們添加到.cpp中。
  • 請注意,標準庫不使用const。 例如std::vector::at(size_type pos) 。 標準庫的好處對我有好處。



我不會把const放在這樣的參數上 - 每個人都知道一個布爾值(而不是布爾值)是不變的,所以把它添加進來會讓人們想“等等,什麼? 甚至你通過引用傳遞參數。




用const記住的事情是,從一開始就把事情變得更加容易,而不是稍後再嘗試。

當你想要某些東西保持不變的時候使用const - 它是一個附加的提示,描述你的函數做什麼以及期待什麼。 我見過很多C API,可以處理其中的一些,特別是接受C字符串的C API!

我更傾向於忽略cpp文件中的const關鍵字,而不是標題,但是由於我傾向於剪切+粘貼它們,所以它們會保存在兩個地方。 我不知道為什麼編譯器允許,我猜它是一個編譯器的東西。 最好的做法是把你的const關鍵字放在兩個文件中。




真的沒有理由做一個值參數“const”,因為函數只能修改變量的一個副本。

使用“const”的原因是,如果你通過引用傳遞更大的東西(例如一個有許多成員的結構),在這種情況下,它確保函數不能修改它。 或者說,如果你試圖用傳統的方式修改它,編譯器會抱怨。 它防止它被意外地改變。




作為參數被按值傳遞的,它沒有任何區別,如果你從調用函數的perspective.It指定const或基本沒有沒有任何意義的數值參數為const申報通。




在你的例子所有consts沒有什麼目的。C ++是通過按值默認,所以函數獲得的那些整數和布爾值的副本。即使該函數修改它們,呼叫者的副本不會受到影響。

所以我想避免額外consts因為

  • 他們redudant
  • 他們弄亂文本
  • 他們阻止我改變傳遞值情況下,它可能是有用的或有效的。



我知道這個問題是“有點”過時但我來到翻過其別人也可能在將來這樣做......我仍然懷疑這可憐的傢伙會列出這裡閱讀我的評論:)

在我看來,我們還是太局限思維的C風格的方式。在OOP PARADIGMA我們玩弄的對象,而不是類型。const對象可以是從非const對象概念上不同的,特別是在邏輯const的(與按位常數)的意義。因此,即使功能則params的const正確性是(可能)的過細緻在莢果的情況下,它不是那麼在對象的情況下。如果一個函數與一個const對象的工作就應該這麼說。考慮下面的代碼片段

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

PS:你可能會說,(常量)引用將在這裡更合適,並為您提供了相同的行為。恩,對。只是讓從我能看到其他地方不同的畫面...




作為一個VB.NET程序員需要使用C ++程序與50+公開的函數,並且偶爾使用const限定符.h文件時,難以知道什麼時候訪問使用的ByRef或BYVAL的變量。

當然,程序會告訴你通過你在哪裡犯了錯行產生異常錯誤,但你需要猜測其中2-10參數是錯誤的。

所以,現在我有試圖說服開發者,他們應該真正以允許創建所有VB.NET函數定義很容易的自動方法定義其變量(在.h文件)令人討厭的任務。然後,他們會得意地說,“讀...文檔。”

我寫了解析.h文件,並創建所有聲明函數命令的awk腳本,但沒有一個指標哪個變量R / O對R / W,它只做了一半工作。

編輯:

在我加入以下其他用戶的鼓勵;

下面是形成不良的條目.H一個(IMO)的一個例子;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

從我的腳本生成的VB;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

注意第一個參數丟失的“常量”。沒有它,一個程序(或其他開發商)不知道的第一個參數應通過“BYVAL。” 通過添加“常量”它使.h文件自我記錄,以便使用其他語言的開發人員可以很容易地編寫工作代碼。