C++11:模板函数实现支持变长参数的简单日志输出

开源的世界,现成的轮子很多,但如果现成的轮子太重太复杂,有的时候也不妨自己发明个轻便的轮子用起来更趁手。

经常我们在程序中需要打调试信息或普通的屏幕输出,大多情况情况下,用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 <string>
#include <vector>
#include <mutex>
#include <regex>
#include <iostream>
#include <type_traits>
#include "string_utils.h"
namespace gdface {
	namespace log {
		// 模板函数,将value输出到stream
		// 非指针类型参数实现
		template<typename E,
			typename TR = std::char_traits<E>,
			typename T>
		typename std::enable_if<!std::is_pointer<T>::value>::type
			_value_output_stream(std::basic_ostream<E, TR>& stream, const T& value) {
			stream << value;
		}
		// 模板函数,将value输出到stream
		// 指针类型参数实现,value为null时输出字符串‘null’
		template<typename E,
			typename TR = std::char_traits<E>, 
			typename T>
		typename std::enable_if<std::is_pointer<T>::value>::type
			_value_output_stream(std::basic_ostream<E, TR>& 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<E>,
			typename AL = std::allocator<E>>
		void _sm_log_output(std::basic_ostream<E, TR>& stream, const std::vector<std::basic_string<E, TR, AL>>& format, int& idx) {
		}
		// 模板递归处理可变参数,每次处理一个参数
		// T 为第一个参数类型
		template<typename E,
			typename TR = std::char_traits<E>,
			typename AL = std::allocator<E>,
			typename T, typename ...Args>
		typename void _sm_log_output(std::basic_ostream<E, TR>& stream, const std::vector<std::basic_string<E, TR, AL>>& 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<E>,
			typename AL = std::allocator<E>,
			typename _str_type = std::basic_string<E, TR, AL>,
			typename ...Args>
		void sm_log(std::basic_ostream<E, TR>& stream, const char* file, int line, const std::basic_string<E, TR, AL>& 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<E>,
			typename AL = std::allocator<E>,
			typename ...Args>
			void sm_log(std::basic_ostream<E, TR>& stream, const char* file, int line, const E* format, Args...args) {
			sm_log(stream, file, line, std::basic_string<E, TR, AL>(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输出测试");
}

windows下输出:
这里写图片描述
linux下输出
这里写图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值