欧美性猛交黑人xxxx,成人毛片一区二区三区,久久综合九色综合精品,男的把j放进女人下面视频免费

C++箴言:只要可能就用const

  • 發布于:2023-09-04
  • 192 人圍觀

  關于 const 的一件美妙的事情是它允許你指定一種語義上的約束:一個特定的對象不應該被修改。而編譯器將執行這一約束。它允許你通知編譯器和其他程序員,某個值應該保持不變。如果確實如此,你就應該明確地表示出來,因為這樣一來,你就可以謀取編譯器的幫助,確定這個值不會被改變。

  關鍵字 const 非常多才多藝。在類的外部,你可以將它用于全局常量或命名空間常量,就像那些在文件、函數或模塊范圍內被聲明為 static 的對象。在類的內部,你可以將它用于 static 和 non-static 數據成員上。對于指針,你可以指定這個指針本身是 const,或者它所指向的數據是 const,或者兩者都是,或者都不是。

char greeting[] = "Hello";

char *p = greeting; // non-const pointer,
// non-const data

const char *p = greeting; // non-const pointer,
// const data

char * const p = greeting; // const pointer,
// non-const data

const char * const p = greeting; // const pointer,
// const data

  這樣的語法本身其實并不像表面上那樣反復無常。如果 const 出現在 * 左邊,則指針指向的內容為常量;如果 const 出現在 * 右邊,則指針自身為常量;如果 const 出現在 * 兩邊,則兩者都為常量。

  當指針指向的內容為常量時,一些人將 const 放在類型之前,另一些人將 const 放在類型之后 * 之前。兩者在意義上并沒有區別,所以,如下兩個函數具有相同的參數類型:

void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object

void f2(Widget const *pw); // so does f2

  因為它們都存在于實際的代碼中,你應該習慣于這兩種形式。

  STL iterators 以指針為原型,所以一個 iterator 在行為上非常類似于一個 T* 指針。聲明一個 iterator 為 const 就類似于聲明一個指針為 const(也就是說聲明一個 T* const 指針):不能將 iterator 指向另外一件不同的東西,但是它所指向的東西本身可以變化。如果你要一個 iterator 指向一個不能變化的東西(也就是 const T* 的 STL 對等物),你應該用 const_iterator:

std::vector<int> vec;
...
const std::vector<int>::iterator iter = // iter acts like a T* const

vec.begin();

*iter = 10; // OK, changes what iter points to

++iter; // error! iter is const

std::vector<int>::const_iterator cIter = //cIter acts like a const T*

vec.begin();

*cIter = 10; // error! *cIter is const

++cIter; // fine, changes cIter

  對 const 最強有力的用法來自于它在函數聲明中的應用。在一個函數聲明中,const 既可以用在函數返回值上,也可以用在個別的參數上,對于成員函數,還可以用于整個函數。

  一個函數返回一個常量,常常可以在不放棄安全和效率的前提下盡可能減少客戶的錯誤造成的影響。例如,考慮在 Item 24 中考察的 rational 成員 operator* 的聲明:

class Rational { ... };

const Rational operator*(const Rational& lhs, const Rational& rhs);

  很多第一次看到這些的人會不以為然。為什么 operator* 的結果應該是一個 const 對象?因為如果它不是,客戶就可以犯下如此暴行:

Rational a, b, c;
...
(a * b) = c; // invoke operator= on the
// result of a*b!

  我不知道為什么一些程序員要為兩個數的乘積賦值,但是我知道很多程序員這樣做也并非不稱職。所有這些可能來自一個簡單的輸入錯誤(要求這個類型能夠隱式轉型到 bool):

if (a * b = c) ... // oops, meant to do a comparison!
  如果 a 和 b 是內建類型,這樣的代碼顯而易見是非法的。一個好的用戶自定義類型的特點就是要避免與內建類型毫無理由的不和諧,而且對我來說允許給兩個數的乘積賦值看上去正是毫無理由的。將 operator* 的返回值聲明為 const 就可以避免這一點,這就是我們要這樣做的理由。

  關于 const 參數沒什么特別新鮮之處——它們的行為就像局部的 const 對象,而且無論何時,只要你能,你就應該這樣使用。除非你需要改變一個參數或本地對象的能力,否則,確認將它聲明為 const。它只需要你鍵入六個字符,就能將你從我們剛剛看到的這個惱人的錯誤中拯救出來:“我想鍵入‘==’,但我意外地鍵入了‘=’”。

  const 成員函數

  成員函數被聲明為 const 的目的是確信這個函數可能會被 const 對象調用。因為兩個原因,這樣的成員函數非常重要。首先,它使一個類的接口更容易被理解。知道哪個函數可以改變對象而哪個不可以是很重要的。第二,它們可以和 const 對象一起工作。書寫高效代碼有一個很重要的方面,就像 Item 20 所解釋的,提升一個 C++ 程序的性能的基本方法就是就是傳遞一個對象的引用給一個 const 參數。這個技術只有在 const 候選對象有 const 成員函數可操作時才是可用的。

  很多人沒有注意到這樣的事實,即成員函數只有常量性不同時是可以被重載的,這是 C++ 的一個重要特性。考慮一個代表文字塊的類:

class TextBlock {

  public:
   ...
   const char& operator[](std::size_t position) const // operator[] for
   { return text[position]; } // const objects
   char& operator[](std::size_t position) // operator[] for
   { return text[position]; } // non-const objects
  private:
   std::string text;
};

  TextBlock 的 operator[]s 可能會這樣使用:

TextBlock tb("Hello");

std::cout << tb[0]; // calls non-const
// TextBlock::operator[]

const TextBlock ctb("World");

std::cout << ctb[0]; // calls const TextBlock::operator[]

  順便提一下,const 對象在實際程序中最經常使用的是作為這樣一個操作的結果:將指針或者引用傳遞給 const 參數。上面的 ctb 的例子是人工假造的。下面這個例子更真實一些:

void print(const TextBlock& ctb) // in this function, ctb is const
{
  std::cout << ctb[0]; // calls const TextBlock::operator[]
  ...
}

  通過將 operator[] 重載,而且給不同的版本不同的返回類型,你能對 const 和 non-const 的 TextBlocks 做不同的操作:

std::cout << tb[0]; // fine - reading a
// non-const TextBlock

tb[0] = ’x’; // fine - writing a
// non-const TextBlock

std::cout << ctb[0]; // fine - reading a
// const TextBlock

ctb[0] = ’x’; // error! - writing a
// const TextBlock

  請注意這個錯誤只是發生在調用 operator[] 的返回類型上,而調用 operator[] 本身總是正確的。錯誤出現在企圖為 const char& 賦值的時候,而這正是 const 版本的 operator[] 的返回類型。

  再請注意 non-const 版本的 operator[] 的返回類型是一個字符的引用而不是字符本身。如果 operator[] 只是簡單地返回一個字符,下面的語句將無法編譯:

tb[0] = ’x’;
  因為改變一個返回內建類型的函數的返回值總是非法的。如果它合法,那么 C++ 以值(by value)返回對象這一事實(參見 Item 20)就意味著 tb.text[0] 的副本被改變,而不是 tb.text[0] 自己,這不會是你想要的行為。

  讓我們為哲學留一點時間。看看一個成員函數是 const 意味著什么?有兩個主要的概念:二進制位常量性(bitwise constness)(也稱為物理常量性(physical constness))和邏輯常量性(logical constness)。

  二進制位 const 派別堅持認為,一個成員函數,當且僅當它不能改變對象的任何數據成員(static 成員除外),也就是說不能改變對象內的任何二進制位,則這個成員函數就是 const。二進制位常量性的一個好處是比較容易監測違例:編譯器只需要尋找對數據成員的賦值。實際上,二進制位常量性就是 C++ 對常量性的定義,一個 const 成員函數不被允許改變調用它的對象的任何 non-static 數據成員。

  不幸的事,很多成員函數并不能完全通過二進制位常量性的檢驗。特別是,一個經常改變一個指針指向的內容的成員函數。除非這個指針在這個對象中,否則這個函數就是二進制位 const 的,編譯器也不會提出異議。例如,假設我們有一個類似 TextBlock 的類,因為它需要與一個不知 string 為何物的 C API 打交道,所以它需要將它的數據存儲為 char* 而不是 string。

class CTextBlock {
  public:
   ...
   char& operator[](std::size_t position) const // inappropriate (but bitwise

   { return pText[position]; } // const) declaration of
   // operator[]
  private:
   char *pText;
};

  盡管 operator[] 返回對象內部數據的引用,這個類還是(不適當地)將它聲明為 const 成員函數(Item 28 將談論一個深入的主題)。先將它放到一邊,看看 operator[] 的實現,它并沒有使用任何手段改變 pText。結果,編譯器愉快地生成了 operator[] 的代碼,因為對所有編譯器而言,它都是二進制位 const 的,但是我們看看會發生什么:

const CTextBlock cctb("Hello"); // declare constant object

char *pc = &cctb[0]; // call the const operator[] to get a
// pointer to cctb’s data

*pc = ’J’; // cctb now has the value "Jello"

  這里確實出了問題,你用一個確定的值創建一個常量對象,然后你只是用它調用了 const 成員函數,但是你改變了它的值! 這就引出了邏輯常量性的概念。這一理論的信徒認為:一個 const 成員函數被調用的時候可能會改變對象中的一些二進制位,但是只能用客戶無法感覺到的方法。例如,你的 CTextBlock 類在需要的時候可以儲存文字塊的長度:

class CTextBlock {
  public:
   ..
   std::size_t length() const;

  private:
   char *pText;
   std::size_t textLength; // last calculated length of textblock
   bool lengthIsValid; // whether length is currently valid
};

std::size_t CTextBlock::length() const
{
  if (!lengthIsValid) {
   textLength = std::strlen(pText); // error! can’t assign to textLength
   lengthIsValid = true; // and lengthIsValid in a const
  } // member function
  return textLength;
}

  length 的實現當然不是二進制位 const 的—— textLength 和 lengthIsValid 都可能會被改變——但是它還是被看作對 const CTextBlock 對象有效。但編譯器不同意,它還是堅持二進制位常量性,怎么辦呢?

  解決方法很簡單:利用以關鍵字 mutable 為表現形式的 C++ 的 const-related 的靈活空間。mutable 將 non-static 數據成員從二進制位常量性的約束中解放出來:

class CTextBlock {
  public:
   ...
   std::size_t length() const;

  private:
   char *pText;
   mutable std::size_t textLength; // these data members may
   mutable bool lengthIsValid; // always be modified, even in
}; // const member functions

std::size_t CTextBlock::length() const
{
  if (!lengthIsValid) {
   textLength = std::strlen(pText); // now fine
   lengthIsValid = true; // also fine
  }
  return textLength;
}

  避免 const 和 non-const 成員函數的重復

  mutable 對于解決二進制位常量性不太合我的心意的問題是一個不錯的解決方案,但它不能解決全部的 const-related 難題。例如,假設 TextBlock(包括 CTextBlock)中的 operator[] 不僅要返回一個適當的字符的引用,它還要進行邊界檢查,記錄訪問信息,甚至數據完整性確認,將這些功能加入到 const 和 non-const 的 operator[] 函數中,使它們變成如下這樣的龐然大物:

class TextBlock {
  public:
   ..
   const char& operator[](std::size_t position) const
   {
    ... // do bounds checking
    ... // log access data
    ... // verify data integrity
    return text[position];
   }
   char& operator[](std::size_t position)
   {
    ... // do bounds checking
    ... // log access data
    ... // verify data integrity
    return text[position];
   }
  private:
   std::string text;
};

  哎呀!你是說重復代碼?還有隨之而來的額外的編譯時間,維護成本以及代碼膨脹等令人頭痛的事情嗎?當然,也可以將邊界檢查等全部代碼轉移到一個單獨的成員函數(當然是私有的)中,并讓兩個版本的 operator[] 來調用它,但是,你還是要重復寫出調用那個函數和返回語句的代碼。

  怎樣才能只實現一次 operator[] 功能,又可以使用兩次呢?你可以用一個版本的 operator[] 去調用另一個版本。并通過強制轉型去掉常量性。

  作為一個通用規則,強制轉型是一個非常壞的主意,我將投入整個一個 Item 來告訴你不要使用它,但是重復代碼也不是什么好事。在當前情況下,const 版本的 operator[] 所做的事也正是 non-const 版本所做的,僅有的不同是它有一個 const 返回類型。在這種情況下,通過轉型去掉返回類型的常量性是安全的,因為,無論誰調用 non-const operator[],首要條件是有一個 non-const 對象。否則,他不可能調用一個 non-const 函數。所以,即使需要一個強制轉型,讓 non-const operator[] 調用 const 版本以避免重復代碼的方法也是安全的。代碼如下,隨后的解釋可能會讓你對它的理解更加清晰:

class TextBlock {
  public:
   ...
   const char& operator[](std::size_t position) const // same as before
   {
    ...
    ...
    ...
    return text[position];
   }
   char& operator[](std::size_t position) // now just calls const op[]
   {
    return
    const_cast<char&>( // cast away const on
    // op[]’s return type;
    static_cast<const TextBlock&>(*this) // add const to *this’s type;
    [position] // call const version of op[]
   );
  }
  ...
};

  正如你看到的,代碼中有兩處強制轉型,而不止一處。我們讓 non-const operator[] 調用 const 版本,但是,如果在 non-const operator[] 的內部,我們僅僅調用了 operator[],那我們將遞歸調用我們自己一百萬次甚至更多。為了避免無限遞歸,我們必須明確指出我們要調用 const operator[],但是沒有直接的辦法能做到這一點,于是我們將 *this 從它本來的類型 TextBlock& 強制轉型到 const TextBlock&。是的,我們使用強制轉型為它加上了 const!所以我們有兩次強制轉型:第一次是為 *this 加上 const(目的是當我們調用 operator[] 時調用的是 const 版本),第二次是從 const operator[] 的返回值之中去掉 const。

  增加 const 的強制轉型是一次安全的轉換(從一個 non-const 對象到一個 const 對象),所以我們用 static_cast 來做。去掉 const 的強制轉型可以用 const_cast 來完成,在這里我們沒有別的選擇。

  在完成其它事情的基礎上,我們在此例中調用了一個操作符,所以,語法看上去有些奇怪。導致其不會贏得選美比賽,但是它通過在 const 版本的 operator[] 之上實現其 non-const 版本而避免重復代碼的方法達到了預期的效果。使用丑陋的語法達到目標是否值得最好由你自己決定,但是這種在一個 const 成員函數的基礎上實現它的 non-const 版本的技術卻非常值得掌握。

  更加值得知道的是做這件事的反向方法——通過用 const 版本調用 non-const 版本來避免代碼重復——是你不能做的。記住,一個 const 成員函數承諾不會改變它的對象的邏輯狀態,但是一個 non-const 成員函數不會做這樣的承諾。如果你從一個 const 成員函數調用一個 non-const 成員函數,你將面臨你承諾不會變化的對象被改變的風險。這就是為什么使用一個 const 成員函數調用一個 non-const 成員函數是錯誤的,對象可能會被改變。實際上,那樣的代碼如果想通過編譯,你必須用一個 const_cast 來去掉 *this 的 const,這樣做是一個顯而易見的麻煩。而反向的調用——就像我在上面的例子中用的——是安全的:一個 non-const 成員函數對一個對象能夠為所欲為,所以調用一個 const 成員函數也沒有任何風險。這就是 static_cast 可以在這里工作的原因:這里沒有 const-related 危險。

  就像在本文開始我所說的,const 是一件美妙的東西。在指針和迭代器上,在涉及對象的指針,迭代器和引用上,在函數參數和返回值上,在局部變量上,在成員函數上,const 是一個強有力的盟友。只要可能就用它,你會為你所做的感到高興。

  Things to Remember

  ·將某些東西聲明為 const 有助于編譯器發現使用錯誤。const 能被用于對象的任何范圍,用于函數參數和返回類型,用于整個成員函數。

  ·編譯器堅持二進制位常量性,但是你應該用概念上的常量性(conceptual constness)來編程。(此處原文有誤,conceptual constness 為作者在本書第二版中對 logical constness 的稱呼,正文中的稱呼改了,此處卻沒有改。其實此處還是作者新加的部分,卻使用了舊的術語,怪!——譯者)

  ·當 const 和 non-const 成員函數具有本質上相同的實現的時候,使用 non-const 版本調用 const 版本可以避免重復代碼。

萬企互聯
標簽: