最近需要提供一个功能,采用类似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中, 提供了对可变参数检查的机制,见
这里.