开源的世界,现成的轮子很多,但如果现成的轮子太重太复杂,有的时候也不妨自己发明个轻便的轮子用起来更趁手。
经常我们在程序中需要打调试信息或普通的屏幕输出,大多情况情况下,用printf
就可以将就了,但printf
用志来也不是太方便:
需要为不同的参数类型指定不同的输出格式(%s,%d....
),错了还不行,好麻烦,为了调试方便需要在文件名和行号,printf可做不到这个。
我们知道,log4cpp
,glog
都为C++程序提供了强大的日志输出功能,能实现远远超上面的很多功能,但是有的时候我真的不需要这么强的日志输出,而且引入这些第三方库也要折腾一阵子,如果程序要支持跨平台编译,设置还要复杂一些。
为了解决这个问题,我想到基于C++11的变长参数模板,自己实现一个简单的控制台信息输出功能。
关于变长参数模板,现在也有不少入门的文章介绍,不了解概念的童鞋可以搜索一下,随便找一篇供参考:
《使用C++11变长参数模板 处理任意长度、类型之参数实例》
变长模板、变长参数C++11提供的新特性,利用变长参数模板,可以处理任意长度、类型的参数实例。有这个语言特性的帮助,就可以像java语言一样,定义可以接收任意长度不同类型的参数的函数。
sample_log.h
#ifndef COMMON_SOURCE_CPP_SAMPLE_LOG_H_
#define COMMON_SOURCE_CPP_SAMPLE_LOG_H_
#include
#include
#include
#include
#include
#include
#include "string_utils.h"
namespace gdface {
namespace log {
// 模板函数,将value输出到stream
// 非指针类型参数实现
template<typename E,
typename TR = std::char_traits,
typename T>
typename std::enable_ifstd::is_pointer::value>::type
_value_output_stream(std::basic_ostream& stream, const T& value) {
stream << value;
}
// 模板函数,将value输出到stream
// 指针类型参数实现,value为null时输出字符串‘null’
template<typename E,
typename TR = std::char_traits,
typename T>
typename std::enable_if<std::is_pointer::value>::type
_value_output_stream(std::basic_ostream& stream, const T& value) {
// 为 null的指针输出 字符串'null'
if (nullptr == value) {
stream << "null";
}
else {
stream << value;
}
}
// 特化函数
// 当value为string时转为wstring输出到wostream
inline void _value_output_stream(std::wostream&stream, const std::string& value) {
stream << to_wide_string(value);
}
// 特化函数
// 当value为wstring时转为string输出到ostream
inline void _value_output_stream(std::ostream&stream, const std::wstring& value) {
stream << to_byte_string(value);
}
// 特化函数
// 当value为wchar_t*时转为string输出到ostream
inline void _value_output_stream(std::ostream&stream, const wchar_t* value) {
if (nullptr == value) {
stream << "null";
}
else {
stream << to_byte_string(value);
}
}
/* 终止递归函数 */
template<typename E,
typename TR = std::char_traits,
typename AL = std::allocator>
void _sm_log_output(std::basic_ostream& stream, const std::vector<std::basic_string >& format, int& idx) {
}
// 模板递归处理可变参数,每次处理一个参数
// T 为第一个参数类型
template<typename E,
typename TR = std::char_traits,
typename AL = std::allocator,
typename T, typename ...Args>
typename void _sm_log_output(std::basic_ostream& stream, const std::vector<std::basic_string >& format, int& idx, const T& first, Args...rest) {
if (idx < format.size()) {
_value_output_stream(stream, format[idx]);
if (idx < format.size() - 1) {
_value_output_stream(stream, first);
}
_sm_log_output(stream, format, ++idx, rest...);
}
}
// 调用递归模板函数_sm_log_output 输出所有可变参数
// E为基本元素数据类型,支持char,wchar_t,
// 对应的stream支持ostream,wostream,fromat支持string,wstring
template<typename E,
typename TR = std::char_traits,
typename AL = std::allocator,
typename _str_type = std::basic_string,
typename ...Args>
void sm_log(std::basic_ostream& stream, const char* file, int line, const std::basic_string& format, Args...args) {
const static std::string delim("{}");
static std::once_flag oc;
std::call_once(oc, [] {
#ifdef _MSC_VER
std::locale loc(std::locale(), "", LC_CTYPE);
std::wcout.imbue(loc);
std::wcerr.imbue(loc);
std::wclog.imbue(loc);
#elif defined(__GNUC__)
std::locale::global(std::locale(""));
#endif
});
// {}为占位符
auto vf = split(format, std::string("\\{\\}"));
if (end_with(format, delim)) {
// 末尾插入空字符串
vf.push_back(_str_type());
}
std::string fn(file);
auto pos = fn.find_last_of("\\/");
// 只显示文件名
int index = 0;
stream << "[" << (pos != std::string::npos ? fn.substr(pos + 1) : fn).c_str() << ":" << line << "]:";
// 调用递归模板函数
_sm_log_output(stream, vf, index, args...);
// 输入参数 少于占位符数目,则原样输出格式化字符串
for (; index < vf.size(); ++index) {
stream << vf[index];
if (index < vf.size() - 1) {
stream << "{}";
}
}
stream << std::endl;
}
// 局部特化函数
// 当format为指针类型时,转为wstring或string
template<typename E,
typename TR = std::char_traits,
typename AL = std::allocator,
typename ...Args>
void sm_log(std::basic_ostream& stream, const char* file, int line, const E* format, Args...args) {
sm_log(stream, file, line, std::basic_string(format), args...);
}
// 局部特化函数,
// 当format为string类型而stream为wostream类型时,将format转为wstring
template<typename ...Args>
void sm_log(std::wostream& stream, const char* file, int line, const char* format, Args...args) {
sm_log(stream, file, line, to_wide_string(format), args...);
}
} /* namespace log */
// 定义使用 ostream 还是 wostream作为输出流
// 默认使用 wostream 输出,以确保宽字符集信息(如中文)可正确显示
#ifdef _SL_USE_BYTE_STREAM
#define __SL_STREAM_OUT__ std::cout
#define __SL_STREAM_ERR__ std::cerr
#define __SL_STREAM_LOG__ std::clog
#else
#define __SL_STREAM_OUT__ std::wcout
#define __SL_STREAM_ERR__ std::wcerr
#define __SL_STREAM_LOG__ std::wclog
#endif
#define SAMPLE_LOG_STREAM(stream,format,...) gdface::log::sm_log(stream,__FILE__,__LINE__,format, ##__VA_ARGS__)
// 向std::cout输出带文件名和行号的信息,{}为占位符,调用示例
// SAMPLE_LOG("hello,{} {}","world",2018);
// 输出:hello,world 2018
// NOTE:
// 因为gdface::log::sm_log函数中调用了std::call_once函数,
// 所以在linux下编译时务必要加 -lpthread 选项,否则运行时会抛出异常:
// terminate called after throwing an instance of 'std::system_error'
// what() : Unknown error - 1
#define SAMPLE_OUT(format,...) SAMPLE_LOG_STREAM(__SL_STREAM_OUT__,format, ##__VA_ARGS__)
#define SAMPLE_ERR(format,...) SAMPLE_LOG_STREAM(__SL_STREAM_ERR__,format, ##__VA_ARGS__)
#define SAMPLE_LOG(format,...) SAMPLE_LOG_STREAM(__SL_STREAM_LOG__,format, ##__VA_ARGS__)
} /* namespace gdface */
#endif /* COMMON_SOURCE_CPP_SAMPLE_LOG_H_ */
完整代码参见gitee仓库:
https://gitee.com/l0km/common_source_cpp/blob/master/sample_log.h
上面代码#include "string_utils.h"
的文件在gitee仓库地址:
https://gitee.com/l0km/common_source_cpp/blob/master/string_utils.h
上面的实现代码有一百多行,真正供我们调用的其实就是最后定义的三个宏SAMPLE_OUT,SAMPLE_ERR,SAMPLE_LOG
,用法类似于log4j
。如下:
#include "sample_log.h"
int main() {
const wchar_t * wcp = L"[char pointer汉字]";
double pi = 3.14159265358979323846;
// string,wstring,pointer,number类型测试
SAMPLE_OUT("{}std::wcout输出测试 wchar_t*:{} pointer = {} double:{} chinese char*:{}", "hello,", wcp, &pi, pi,"error程序猿");
// 当输入参数多于{} 占位符时,多余的参数不显示
SAMPLE_OUT("{}std::wcout输出测试 wchar_t*:{} ", "hello,", wcp, &pi, pi);
// 当输入参数少于{} 占位符时,显示多余的占位符
SAMPLE_OUT("{}std::wcout输出测试 wchar_t*:{} pointer = {} double:{} chinese char*:{}", "hello,", wcp);
SAMPLE_OUT("ERROR: {}", "std::wcerr输出测试");
SAMPLE_LOG("LOG: {}", "std::wclog输出测试");
}