幫 C++ 的 output stream 加上 callback

| | 0 Comments| 09:12
Categories:

這篇某種意義上算是之前《攔截 std::cout 的輸出結果》的延伸。

在把 std::cout 等內容攔截下來後,自然就是要想辦法拿來用了。但是要怎麼知道裡面有新東西、該去讀資料呢?

由於 std::streambuf參考)的設計應該是沒有辦法掛 callback 之類的東西,所以如果想要有類似的功能,應該就是得自己去擴展一個有 callback 功能的 stream buffer 了。

下面就是一個簡單的例子,會每行執行一次 callback:

class funcStrBuf : public std::streambuf
{
public:
  funcStrBuf(std::function<void(std::string_view)> f) : std::streambuf()
  {
    funcCallback = f;
  }
 
protected:
  int_type overflow(int_type ch) override
  {
    if (ch == '\n')
      runCallback();
    else
      sContent += ch;
    
    return std::streambuf::traits_type::not_eof(ch);
  }
 
  int sync() override
  {
    runCallback();
    return 0;
  }
 
  void runCallback()
  {
    if (sContent.size() > 0)
    {
      funcCallback(sContent);
      sContent.clear();
    }
  }
 
protected:
  std::string sContent;
  std::function<void(std::string_view)>  funcCallback = [](std::string_view) {};
};

這邊的 funcStrBuf 這個類別內部是使用 sContent 這個字串來做為內部的記憶體管理;而當有東西輸出給他的時候,就會每個字元一個一個透過 overflow() 這個函式(參考)送進來,所以這邊必須要重新實做這個函式來做自己的處理。

這邊由於是打算每行執行一次,所以這邊的 overflow() 就是去檢查拿到的字元是否是換行字元的「\n」(\r 這邊先不管),如果是的話就去呼叫 runCallback() 這個函式;否則就把字元附加到 sContent 後面。

而在 runCallback() 中,則是先檢查 sContent 是否是空字串,如果不是的話就去呼叫 callback function 的物件 funcCallback、然後再把 sContent 清空。

另外,這邊也重新實做了 sync() 這個函式(參考),主要是用來對應 flush 這類的動作;這個其實不見得需要,基本上是看個人的需求了。

像如果還是要用來攔截 std::cout 的結果來使用的話,大致上會是像下面的形式:

// get standard buffer
auto* pStdBuf = std::cout.rdbuf();
 
// use another streambuf
std::vector<std::string> vData;
funcStrBuf buf([&vData](std::string_view sv) {
  vData.emplace_back(sv);
});
 
//functionbuf buf;
std::cout.rdbuf(&buf);
 
// output
std::cout << "xxx" << std::flush;
std::cout << "Hello world" << std::endl;
std::cout << 1 << ":" << 2.5 << "\n" << 'a' << "as" << std::endl;
 
// restore buffer
std::cout.rdbuf(pStdBuf);
 
// output
for(const auto& s : vData)
  std::cout << "[" << s << "]\n";

這邊基本上就是和之前一樣、透過 rdbuf() 這個函式來重新設定 std::cout 的輸出 buffer;而這邊則是使用前面定義的 funcStrBuf,所設定的 callback 基本上就是把拿到的字串丟到 vData 這個陣列裡面儲存下來,最後在一起輸出。

這樣的程式在執行後,結果會像下面這樣:

[xxx]
[Hello world]
[1:2.5]
[aas]

而如果有需求的話,也可以在 callback 被執行的時候,加上時間資訊之類的,這樣拿來分析輸出的資訊就更有用了。

如果要搭配圖形介面顯示的話,也可以把拿到的文字丟到圖形介面裡面來顯示;實際上 Heresy 這邊也有拿來搭配 Qt 使用,應該是沒問題的。


附註:由於 std::stringbuf 雖然有提供內部資料的管理,但是在呼叫內部的函式時會做一些額外的處理,所以不建議繼承 std::stringbuf 來開發。


參考:c++ execute function any time a stream is written to

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *