2013年12月14日 星期六

初學者指南:如何客製化自己的 streambu

This article is revised from http://www.mr-edd.co.uk/blog/beginners_guide_streambuf

這篇廢話很多,所以我砍掉廢話,寫了精簡版。

Background of Stream Buffer

在 C++ STL 中,stream 只是一種操作界面,其實並不負責真正的資料搬移動作。真正的資料搬移,是在一個特別的類別 - streambuf 當中完成。舉個例子來說,ofstream 並不負責把資料搬到檔案當中,真正負責搬資料動作的,是 filebuf,但是ofstream決定資料搬移的時機;類似的是 stringtream 和 stringbuf 的關係,stringbuf 負責真正的資料搬移,而stringstream決定了搬移的時機。
上圖是從 cplusplus.com 所剪出來的圖,從這圖中,我們可以看到很清楚的概念。ostream與istream提供了所有 stream 的抽象界面,而streambuf提供了buffer的抽象界面。ostream/istream與buffer的關係,是所謂的strategy,一旦更換buffer,則ostream/istream就彷彿換了個行為一般。fstream與stringstream就是利用這個方式,藉由filebuf與stringbuf來修改ostream與istream的行為。

std::streambuf 在概念上,就是一個 array,被 stream 物件所使用。input stream 會 re-filled 這個 array;或者 output stream 會 flush 且清空這個 array。
當插入一筆資料到 ostream,這筆資料就會被寫進 buffer 的 array。當這 array overflows,那比資料就會被 flush 到目的地。並且此 array 的狀態會被重新設定,好寫入更多的資料。

重點提示

  • 所有的 stream buffer 都是繼承 std::streambuf base class。衍生物件必須要透過 override std::streambuf 的 virtual functions 來識做自己的特殊行為。
  • stream buffer 就是一個 array,當資料都被移出 buffer 時,istream 會重新 fill 這個 array;而當資料都被移入 buffer 之後,ostream 會呼叫 flush 並且清空這個 array。

Internal Array

Streambuf 使用了六個 pointer 來維持 array的資料 - 三個 for input,三個 for output。

給 ostream 使用的 pointer 有
  • pbase() - 指向 array 的第一個 element
  • pptr() - put pointer,指向下一個被 ostream 寫入的 element
  • eppter() - the end of array
outbuf_pointers.png
一般來說,pbase() 和 epptr() 不會改變,只有 pptr() 會隨著 ostream 不斷地寫入而改變。

Input pointer 有:
  • eback() - 指向 array 的最後一個 element。
  • gptr() - the get pointer - 指向 buffer 下一個應該被送入 istream 的 element
  • epgtr() -the end of array
inbuf_pointers.png
相同的,eback() 與 epgtr() 一般來說不會改變。只有 gptr 會隨著 istream 不斷地讀取資料而改變。

Example 1 - New OStream

這例子當中,我們的 buffer 是要給 ostream 使用。每次 ostream 呼叫 operator << 時,我們便將資料搬到 buffer 當中。當 buffer 已經滿了,無法再寫入時,會呼叫 overflow()。

我們只需要 override overflow() 和 sync()。當 pptr() == epptr() 的時候,overflow 會被執行。overflow() 應該要把 contents (包含發生 overflow 的那個 CharT) 寫入資料當中,並且 return 一個不是 traits_type::eof() 的直回去。

sync() 負責把目前的 data 寫入 target 當中,即使 buffer 尚未 full。舉例來說,如果 std::flush 被寫入了,應該要立即寫入 target。如果 sync() 失敗,應該要 return -1。

setg用來設定前述的 ostream buffer,我們只需要設定 pbase() 和 epptr()。系統會自動設定 pptr() 為 pbase()。