從C語言說起
在C語言的時候,const的用途很簡單,用來修飾變數的屬性。以下給個範例
1 | void func(const int *v) |
上面的*v = 10
會被編譯器指出v是不能被修改的。 值得注意的是 const int *
的寫法跟 int const *
是一樣的,不過我比較偏好前者。
如果改寫成
1 | void func(int * const v) |
會告訴你v = &p
這行錯了,由此可見int const *
表示被指向的 內容 不可改,而指標是可以改變的,這邊的const是用來修飾int
的。而int * const
表示指向的 指標 不可改,而這邊的const是用來修飾int *
的。
當然,如果要兩者間得,也可以寫成這樣
1 | void func(const int * const v) |
C++98
C++擴大const的使用範圍,允許const修飾Class的Member Function。表示這個函數是 Logic Constness,不影響外界看這物件的狀態。因此以下這段程式碼會出現問題。
1 | class Test { |
由於C++支援Cast Overloading,支援Function Signature相同,但const屬性不同的Overload,因此這樣的是合法的,,
1 | class Test { |
寫個程式來測試一下
1 | oid Test1(const Test &t) |
除了Test1
的Func是跑const版本之外,其他所有函數都是呼叫Non-const版本的Func
雖說const的Member function是不可修改 Logic Constness ,不過以下程式碼很難是合法的。
1 | class Test { |
Mutable
之前說過,從外界看到的類別狀態是 Logic Constness 的,這代表我們可以在 const函數裡面動手腳,只要外界看起來正常就好,因此就有了mutable的誕生。將上面的範例重新改寫。
1 | class Test { |
這樣就能正常使用了…初看之下好像很沒用,不過以這個範例來說
1 | class Test { |
在Multithread的情況之下,有人會呼叫SetState,而有人會想知道GetState的值,雖然state在GetState不會被改變,但是mutex會變。所以需要mutable的存在。
另外一種情形是當做Cache使用
1 | class HashTable { |
不過在C++11之後,又有新用法了
1 | int x = 30; |
f1
在呼叫之後,x會變成123,不過離開f1之後,x又回到30,而f2
呼叫之後就整個變成123了。
不過我看不懂第一個範例的用途是什麼。
constexpr
constexpr是C++11才有的觀念,原先就有Constant Expressions的觀念,不過還是有其不足之處。
假設我們要宣告個n*m的一維陣列,我們會這麼做。
1 | const int n = 5; |
假設我們已經有一個mul2
的函數,試著編譯以下這段程式就會出現錯誤。
1 | const int n = 5; |
結果我們只能藉由以下兩種方法解決,一是回到C語言的Preprocessor來做。
1 | #define mul2(x, y) ((x) * (y)) |
這方式可行,不過缺乏型態資料。在Secure Coding的時候容易出錯。
另一種是在Runtime時算出一個常數。
1 | int mul2(int x, int y) { return x * y; } |
如果要把array儀到global scope,問題又出現了。導致不得不使用Preprocessor的解法。因此就有了constexpr
的誕生。
有了constexpr之後,可以在編譯時期就算出答案,類似Template Metaprogramming,不過用途更廣。
上面的範例我們可以重新改寫
1 | #include <stdio.h> |
可以看到這邊的mul2不只可以用在compile-time,在runtime也可正常執行。
也可以在編譯時期使用物件,上面的範例我們可以用Functor
在寫一次。
1 | const int n = 5; |
如果對constexpr有更多了解,可以參考Constexpr - Generalized Constant Expressions in C++11,目前支援constexpr的編譯器也不多。
結論
C++果然不愧是最難學的語言,每個環節都搞的特別複雜。我難過。