最近需要提供一個功能,采用類似C++流輸出的格式輸出一些日志信息, 例如Log(FATAL) << "log to" .
我找了兩個類似項目來研究,google的
glog 和
log4cpp, 它們都支持以C++流輸出格式進行輸出.
但是研究到最后,我發現最大的問題是, 如果按照C++的流輸出格式進行輸出, 將無法判定需要輸出的信息到哪里是結束.比如log << "hello " << "world",是無法判斷到底在輸出"hello"還是"world"的時候上面的參數輸入已經結束了.上面兩個項目中, 解決這個問題的辦法大致是相同的,以下面可編譯運行代碼為例說明它們的做法(在linux g++下面編譯通過):
#include <iostream>
#include <sstream>
#ifdef __DEPRECATED
// Make GCC quiet.
# undef __DEPRECATED
# include <strstream>
# define __DEPRECATED
#else
# include <strstream>
#endif
using namespace std;
class LoggerStream : public std::ostrstream {
public:
LoggerStream(char * buf, int len)
: ostrstream(buf, len),
buf_(buf),
len_(len) {
}
~LoggerStream() {
// do the real fucking output
cout << buf_;
}
private:
char *buf_;
int len_;
};
int main() {
char buf[100] = {'\0'};
LoggerStream(buf, sizeof(buf)) << 1 << " hello world\n";
cout << "buf = " << buf << endl;
return 0;
}
在上面的代碼中, 開始進行輸出的時候首先初始化一個LoggerStream對象, 而在輸出參數輸入完畢的時候將調用它的析構函數,在這個析構函數中才完成真正的輸出動作.也就是說,由于對輸入參數結束位置判斷手段的缺失,C++中不得不采用這個手段在析構函數中完成最終的輸出工作.
這樣的做法,最大的問題是,頻繁的構造/析構開銷大,而且每個"<<"操作符背后又需要調用ostream的operator<<,也就是假如你的輸入參數有三個將調用operator <<三次(當然是經過重載的,不一定都是同一個operator<<),因此,假如需要考慮多線程的話,那么一次輸入有多個函數函數中被調用,仍然是問題.天,要使用這門語言寫出正確的程序來,需要了解底下多少的細節呢?!
最后,我向項目組反映這個問題,一致同意以C中類似sprintf可變參數的形式實現這個功能.可變參數解決這個問題,就我的感覺而言,就是輸入參數的時候,稍顯復雜,需要用戶指定輸入的格式.然而,其實這個做法也有好處:作為函數的使用者,你必須明確的知道你在做什么并且反饋給你所使用的函數.明確的,無歧義的使用函數,而不是依靠所謂函數重載猜你的用意,我想也是避免問題的一個手段.gcc中, 提供了對可變參數檢查的機制,見
這里.