最近需要提供一個(gè)功能,采用類似C++流輸出的格式輸出一些日志信息, 例如Log(FATAL) << "log to" .
我找了兩個(gè)類似項(xiàng)目來研究,google的
glog 和
log4cpp, 它們都支持以C++流輸出格式進(jìn)行輸出.
但是研究到最后,我發(fā)現(xiàn)最大的問題是, 如果按照C++的流輸出格式進(jìn)行輸出, 將無法判定需要輸出的信息到哪里是結(jié)束.比如log << "hello " << "world",是無法判斷到底在輸出"hello"還是"world"的時(shí)候上面的參數(shù)輸入已經(jīng)結(jié)束了.上面兩個(gè)項(xiàng)目中, 解決這個(gè)問題的辦法大致是相同的,以下面可編譯運(yùn)行代碼為例說明它們的做法(在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;
}
在上面的代碼中, 開始進(jìn)行輸出的時(shí)候首先初始化一個(gè)LoggerStream對(duì)象, 而在輸出參數(shù)輸入完畢的時(shí)候?qū)⒄{(diào)用它的析構(gòu)函數(shù),在這個(gè)析構(gòu)函數(shù)中才完成真正的輸出動(dòng)作.也就是說,由于對(duì)輸入?yún)?shù)結(jié)束位置判斷手段的缺失,C++中不得不采用這個(gè)手段在析構(gòu)函數(shù)中完成最終的輸出工作.
這樣的做法,最大的問題是,頻繁的構(gòu)造/析構(gòu)開銷大,而且每個(gè)"<<"操作符背后又需要調(diào)用ostream的operator<<,也就是假如你的輸入?yún)?shù)有三個(gè)將調(diào)用operator <<三次(當(dāng)然是經(jīng)過重載的,不一定都是同一個(gè)operator<<),因此,假如需要考慮多線程的話,那么一次輸入有多個(gè)函數(shù)函數(shù)中被調(diào)用,仍然是問題.天,要使用這門語言寫出正確的程序來,需要了解底下多少的細(xì)節(jié)呢?!
最后,我向項(xiàng)目組反映這個(gè)問題,一致同意以C中類似sprintf可變參數(shù)的形式實(shí)現(xiàn)這個(gè)功能.可變參數(shù)解決這個(gè)問題,就我的感覺而言,就是輸入?yún)?shù)的時(shí)候,稍顯復(fù)雜,需要用戶指定輸入的格式.然而,其實(shí)這個(gè)做法也有好處:作為函數(shù)的使用者,你必須明確的知道你在做什么并且反饋給你所使用的函數(shù).明確的,無歧義的使用函數(shù),而不是依靠所謂函數(shù)重載猜你的用意,我想也是避免問題的一個(gè)手段.gcc中, 提供了對(duì)可變參數(shù)檢查的機(jī)制,見
這里.