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 
#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输出测试");
}

windows下输出:
C++11:模板函数实现支持变长参数的简单日志输出_第1张图片
linux下输出
C++11:模板函数实现支持变长参数的简单日志输出_第2张图片

你可能感兴趣的:(c/c++/c++11)