2011年10月28日 星期五

C/C++ const

個人認為 coding 的核心就是管理「物件/函式」之間的 state, 像 namespace (或 Java 的 package)、class、少用全域變數、static 與否等, 都是協助管理 state 的語法。另外, const 也是很有用的語法, 在 C/C++ 這裡用法更靈活。

參考《Const-Correctness in C++》, 筆記如下:

  • const 和 pointer 的關係: 有分目標變數是否為常數, 和指標本身是否為常數兩種, 共有四種組合。詳見之前寫的: 《C 的型別宣告》
  • const_cast: 將常數轉回非常數, 但為造成未定義的行為, 使用情境很窄, 見文末的例子。
  • 基於 "..." 的實際運作情況, 寫成 char const *s = "..." 較好。若使用 char *s = "...", 之後改變 s[3] 會造成未定義結果。該篇說明的例子是, 改變 s[3] 後會改到別的常數字串, 因為是用同一個 string pool (見該篇例子); 而我用 gcc 在 linux 下試的結果是 segmentation fault。
  • const member function 很有意思 (即宣告成 void foo() const), 用它可做到類似 Haskell 的 stateless 的效果, 語意上是不會改變 member fields, 所以 const member function 只能呼叫其它 const member function, 和 Haskell 預設 function 的行為很像, 有助於維護程式。個人相當喜歡這個效果, 可惜 Java 沒有, 得自己用 interface 做到類似效果, 不怎麼直覺。
  • 承上, const member function 的表現行為, 相當於讓 this 的型別變成 Klass const * const this, 其中 Klass 是某個類別名稱。所以在取用 this->xxx = ... 時自然會造成編譯錯誤。附帶一提, 一般 member function 的 this 的型別則是 Klass * const this, 也就是 this 都是常數指標, 不能讓 this 指到別的物件, 即 this = ... 會造成編譯錯誤; const member function 則多了「指到的對像為常數」的限制, 即 this->xxx = ... 會造成編譯錯誤。
  • const member function 有個顯著的缺點, 它無法做 cache。既然知道它不會改 member field, 那應該有很大的機會針對傳入參數做 cache, 省下運算時間。但衝突的是, const member function 就是不能改 member field, 自然也不方便做 cache (用全域變數太髒了)。這時得引入關鍵字 mutable。const member function 可以改變宣告為 mutable 的 member field。

2011-10-30 Update

看完《Effective C++ 3/e》 item 3 談論如何使用 const, 多學到了:

  • const std::vector<int>::iterator iter = vec.begin(); 相當於 int * const
  • std::vector<int>::const_iterator iter = vec.begin(); 才是 int const *
  • 即使用了 const member function, 仍要避免傳出指標, 以免指標指向的內容被改變, 造成 const member function 看似沒有改變內容的假像
  • 若需要同時提供同樣操作, 但有傳回 const 和非 const 的兩種版本, 為了避免重覆程式碼, 可實作傳回 const 的版本, 然後在非 const 的版本用轉型的方式呼叫 const 的版本:
const char& operator[](std::size_t position) const {
    ...
}
char& operator[](std:size_t position) {
    return const_cast<char&>(
        static_cast<const T&>(*this)[position]
    );
}

4 則留言:

  1. char *p = "...";
    我記得這種static allocate好像是read only, 所以會seg fault.

    可以用objdump -x -s -d example.o 看出是在.rodata segment

    回覆刪除
  2. 我之前用 gcc -S 看, 用 objdump 比較方便

    回覆刪除
  3. C++的const是個很麻煩的東西,Java捨棄之,而C#是用 out與ref 來代替。
    通常來說function pointer parameter 中有的用const,有的不用const,是為了指示 caller哪一些參數是當做input,哪些當做output。當做input的參數要保持const,當做output的參數則不const,要被modify。
    但許多人寫程式搞不懂const的適當使用方法,比如有人會寫出
    size_t strlen(char* s)
    漏加const的宣告,或是寫出
    int pow(const int x, const int y)
    多餘const的宣告。

    還不若效法C#,使用 out 或ref 來指示一些參數是用來 output 比較清楚。

    回覆刪除
  4. out 看起來的確比較清楚, 以前在 PL 課本看到 Ada 用 in/out 表示, 覺得滿清楚的, 但大概是大家習慣 C 了, 或是這種寫法不像數學函數, 而沒有流行吧。變成有 coding style 規定統一將 out 參數放到右側, in 放到左側, 算是變通做法, 可是無法定清楚分界。

    至於 int pow(const int x, const int y), 以「用 const 區分 in/out」的角度來看的確是多餘的, 而且會讓函式參數語意不清。我猜會這樣寫, 應該是實作 pow() 的人用來避免自己不小心改到 x 或 y, 也可藉此強迫其它要改 pow() 的人, 不會動到 x 或 y, 增加函式內實作的可讀性 (但顯然和 in/out 的角度相衝)。

    btw, 比較 C++ 和 Python 兩種極端後, 覺得語言定位和取捨的感覺相當鮮明。為了效率, C++ 有一堆微調的修飾字, 之間也會有一些小衝突, 社群/團隊 要有一些規範或習俗, 才方便維護。反之, Python 少了很多微調機會, 以突顯明確的「標準做法」。

    回覆刪除

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...