Do Things the Standard Style
Posted on May 28th, 2008 at 1:28 by fr3@K

見過不少 C/C++ Standard Library 的不愛用者. 要辨認他們並不困難, 從 code 中可以很容易發現:

  1. 在 header file 以 #pragma once 替代標準 inclusion guard 手法
  2. 將一個 const long* 宣告為 CONST LPLONG
  3. 習慣性使用 ZeroMemory/bzeroCopyMemory/bcopy, 而 (無意間?) 迴避 memsetmemcpy 等標準 API

除了省去替 inclusion guard 取有效 (唯一) 名字的功夫之外,1 我實在是看不出上述習慣有任何實質上的好處.



把跟底層系統相關以及上層 UI 的 code 與中間 application 的 code 切開本身就是一件好處多多的事情, 通常也是一件該做的事. 只要模組化切割乾淨再加上使用標準 API, application 層多能不需要什麼修改就可以在多個平台 build and run.

這不但對 project 有 (或許不是立即的) 好處, 熟悉並使用標準的 programmer 常能夠容易地穿梭在不同平台與符合標準的 compiler 之間. 在我看來這對 programmer 也是一樣很有價值的資產.

我也接觸過不少人不喜歡標準容器. 寧願以 array 與其他 linked list 的 implementation 等等來代替標準容器. 他們說:

  1. vector 的迭代速度太慢
  2. 根據我兩個多月前做的 benchmark, 在非 debug 的 configuration 下, vector 迭代的速度一點也不慢 (see benchmark results on GNU/Linux and Windows).

  3. 不能把一個 list 管理的 link node (不是 iterator) 轉移給另一個 list
  4. 實際上是可以的. 見 list::splice.

  5. 標準容器不會自動清除如 SomeContainerTemplate<SomeType*> 管理的 instance
  6. 我們得先搞清楚 SomeContainerTemplate<SomeType*> 所管理的 instance 是指向 SomeType 的 pointer, 而不是 SomeType 的 object. 若需要這樣的功能可以拿 boost::shared_ptr 之類的 smart pointer 搭配標準容器使用2, e.g.:

      SomeContainerTemplate<shared_ptr<SomeType> >
      
  7. vector allocate 的記憶體可能會大於實際所需. 縮小時也不會將多餘的記憶體釋放, 就連 vector::clear() 也一樣
  8. 縮小時不釋放多餘的記憶體其實是一種優化. 把一個有 m elements 的 vector 移除靠後面的 n 個 element 的時候, 不是呼叫 n 次 destructor 跟一次 deallocation 就好了. 是一次 allocation, m - n 次 uninitialized-copy (copy construction at uninitialized memory) 與呼叫 m 次 destructor 再加上一次 deallocation:

    template <class T>
    void shrink_back(
      vector<T>& v,
      size_t n /*number of elements to remove from back*/)
    {
      typename vector<T>::size_type m = v.size();
    
      // Can not remove more than `m` elements.
      n = min(n, m);
    
      // Allocate memory then uninitialized-copy `m` - `n`
      // elements.
      // ps. Pretending the size of allocation is "just
      // enough" to hold `m` - `n` elements, hence smaller
      // then `v`.
      vector<class T> tmp(v.begin(), v.begin() + (m - n));
      // or simply:
      //   vector<class T> tmp(v.begin(), v.end() - n);
    
      swap(v, tmp);
    
      // `tmp` is destructed once gone out of scope:
      //   Calls dtor on `m` elements managed by `tmp`
      //   (previous managed by `v`), then deallocate
      //   memory.
    }
    

    沒錯, 躲不掉的 overhead, 即便是作在 vector 的實作裏面也是一樣. vector 的這個優化讓縮小時只需要做 n 次 uninitialize, 也因此擁有 no-throw 的 exception safety, 一舉兩得. 若真需要釋放多餘的記憶體, 可用下面的方法來取代 vector::clear():

    template <class T>
    void purge(vector<T>& v)
    {
      vector<T> tmp;
      swap(v, tmp);
    }
    

    而 allocate 的記憶體可能會大於實際所需是另一個優化. 以減少當 vector 在長大時發生類似上面 copy and swap 的次數, 儘量跑在沒有直接 allocation 的 code path.

    雖然我自己沒遇過, 這個優化的確在某些有嚴格記憶體限制的應用上可能造成使用者的困擾.

  9. 不熟悉標準容器的介面/行為, 如不清楚 SomeContainer::resize(10) 到底在背後搞了什麼鬼. 或者是覺得標準容器設計的不好
  10. 先不論熟悉標準庫本來就是靠 C++ 吃飯的 programmer 該有的專業技能. 更何況它不是一個你喜不喜歡的問題.

    一般工作上寫的 code 不會是跟著 programmer, 而是屬於公司的. 若那天使用容器的 code 換別人負責, 或是原 programmer 離開服務單位了. 是接手的人通曉標準容器的可能性比較大? 還是剛好也熟悉於前一個 programmer 偏好的某種容器的機率比較高?

標準庫與標準容器都不是完美的, 各家的 implementation 也可能有略微的不同.3 更不是每個人都欣賞它背後的設計哲學. 但它畢竟是個標準, 不但有高度的品質保證, 使用說明與詳細文件更可說是隨手可得. 使用標準容器與 C++ Standard Library, 因為它是掛了保證的最大公因數.

  1. #pragma once 其實也有它潛在的麻煩 []
  2. Boost.PtrContainer 也有這樣的功能. 跟標準容器搭配 boost::shared_ptr 相比各有其優缺點 []
  3. 我正在寫另一篇文字, 討論一個與標準容器相關的 (IMO) 重大瑕疵 []
del.icio.us:Do Things the Standard Style digg:Do Things the Standard Style spurl:Do Things the Standard Style newsvine:Do Things the Standard Style furl:Do Things the Standard Style Y!:Do Things the Standard Style 黑米共享書籤:Do Things the Standard Style 推推王:Do Things the Standard Style
Previous Post
« 不適任的 System Administrator «
Next Post
» CONST LPLONG »

5 Comments »

Comment #4457

我還以為 #pragma once 是個標準的東西說
真是一個大收穫
之前就有發現 boost 都沒用 #pragma once
那時是懷疑是不是有些比較舊的Compiler 不支援
所以才沒用的

Comment by blair.ko — May 29, 2008 @ 11:10


Comment #4463

blair.ko,

按照標準,#pragma「一定」是不標準的東西啊。:-p

C++98: 16.6.1

A preprocessing directive of the form

#pragma pp-token(opt) new-line

causes the implementation to behave in an implementation-defined manner. Any pragma that is not recognized by the implementation is ignored.

Comment by jeffhung — May 30, 2008 @ 18:27


Comment #4464

vector 不會自動釋放記憶體,確實是很麻煩。事實上應該說,很多STL容器在記憶體使用「量」這方面,都不是很好控制。不僅是在small device上,就算在pc上,這也是很重要的issue。

真正的問題應該這麼說:STL在記憶體量的控制做的不好,但用別種container或自己搞,也是一樣沒能夠做的好啊。(除非有針對這點去處理,但一般不會有。)

Comment by jeffhung — May 30, 2008 @ 18:33


Comment #4466

blair,

的確, #pragma 是各 compiler 實作自行定義的. 不過我也是看到 jeffhung 的回應才知道按照標準規範, compiler 是應該要忽略看不懂的 pragma. 我也學到了新東西. :)

Comment by fr3@K — May 31, 2008 @ 15:35


Comment #4467

jeffhung,

我能想到的只有 vector 在 allocate 時的策略 (針對”長大”的優化) 對記憶體的使用量影響較大.

另外如 deque 雖也有類似行為, 但幅度輕微許多. 除非是在記憶體受到極大限制的部份裝置上, 我很難想像 (除了 vector 之外的 container) 會造成實質上的麻煩.

能否請你作更進一步的說明?

Comment by fr3@K — May 31, 2008 @ 15:43


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>