c++(跨平台)实现高自由自定义变长参数日志输出(使用cout,非printf,无需格式化),打印变量名及变量值

先说g++编译器,这部分使用MSVC编译会出错,后续会有修改方案

运用了多项c++新语法(c++11\14\17)折叠表达式、正则表达式分割字符串、变长参数模板等。
打印格式为:“文件名、行数、描述、变量名1:变量值1 变量名2:变量值2 ……”,使用简单,仅需传递变量名即可。

首先看使用部分,后续总共有3个方案实现同样的效果,使用方法不变:
可以看出使用很简单,无需像printf一样需对每个变量考虑变量类型

int main()
{
    int www = 5;
    double sdf = 3.2;
    string ABC = "aasdas";
	TEST_LOG("just test", www, sdf, ABC);
	TEST_LOG("just test2222");
}

期望打印结果为:

file: xx.cpp line: xx just test (www : 5)(sdf : 3.2)(ABC : aasdas)
file: xx.cpp line: xx just test2222 


话不多说,直接上代码,解释见注释。

#include <iostream>
#include <vector>
#include <iterator>
#include <regex>

using namespace std;

template<typename T>
// 该类主要是为了在打印变长参数时,自定义格式,比如给每个变长参数添加空格等,此处是添加括号和变量名
class modifyArgLog
{
public:
    modifyArgLog(T const& r, vector<string> &nameList) : ref(r)
    {
        name = nameList[0];
        nameList.erase(nameList.begin());
    }

    friend ostream& operator<< (ostream& os, modifyArgLog<T> s) {
        return os << "(" << s.name << " : " << s.ref << ')'; // 输出传递进来的参数和括号、变量名
    }
private:
    T const& ref; //指向从构造函数中传递进来的参数
    string name;
};

template<typename ...Args>
void testLog(const char* file, int lineno, string description, string name, Args && ...args)
{
    cout << "file: " << file  << " line: " << lineno << " " << description << " ";

	//  运用正则表达式将传入的变长参数名解析分割到vector中
    regex re { "\\s{0,},\\s{0,}" };
    vector<string> nameList = vector<string> {
        sregex_token_iterator(name.begin(), name.end(), re, -1), sregex_token_iterator() };
        
     //  运用折叠表达式将变长参数输出
	(cout << ... << modifyArgLog(args, nameList)) << endl;
}

//  #args中的#代表取出传入的变长参数的变量名  
//  ##args:如果可变参数被忽略或为空,“##”操作将去除掉它前面的那个逗号,避免编译错误
#define TEST_LOG(description, args...) testLog(__FILE__, __LINE__, description, #args, ##args)



结果:

[Running] cd "c:\Code\C++\Test\test\" && g++ test.cpp -o test -std=c++17 && "c:\Code\C++\Test\test\"test
file: test.cpp line: 44 just test (www : 5)(sdf : 3.2)(ABC : aasdas)
file: test.cpp line: 45 just test2222 

[Done] exited with code=0 in 4.937 seconds

以上代码在VS2017无法通过编译,因为MSVC对可变宏参数的处理和GCC不同,为了使代码具备通用性,可同时在linux和windows平台下使用,因此改进代码如下。

使用方法不变,也就是不改变main函数,核心代码为:(变动部分见注释)

template<typename T>
class modifyArgLog
{
public:
	modifyArgLog(T const& r, vector<string> &nameList) : ref(r)
	{
		name = nameList[0];
		nameList.erase(nameList.begin());
	}

	friend ostream& operator<< (ostream& os, modifyArgLog<T> s) {
		if (s.name == "") {  //此处用空白字符来区分description,保证第一个字符串与变量名打印方式不同
			return os << s.ref << " ";
		}
		return os << "(" << s.name << " : " << s.ref << ')';
	}
private:
	T const& ref;
	string name;
};

template<typename ...Args>
void testLog(const char* file, int lineno, string name, Args && ...args)
{
	cout << "file: " << file << " line: " << lineno << " ";

	regex re{ "\\s{0,},\\s{0,}" };
	vector<string> nameList = vector<string>{
		sregex_token_iterator(name.begin(), name.end(), re, -1), sregex_token_iterator() };
	
	//  宏中去掉公共参数description后,保证宏一定会有至少一个参数,这个参数即原来的description
	//  description之前是在第一行cout中直接打印,现在被放到了args中
	//  为了区分,将传入modifyArgLog的nameList第一个值修改成空字符,就能在类中实现不同打印
	if (!nameList.empty()) {
		nameList[0].clear();
	}

	(cout << ... << modifyArgLog(args, nameList)) << endl;
}

//  GCC中可以通过##__VA_ARGS__去掉逗号,MSVC不支持,所以将description去掉放入可变参数中,保证宏至少有一个参数
//(据说VS2019以上可以通过设置标准预处理器使得支持,此处未实践)
#define TEST_LOG(...) testLog(__FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)

VS2017中的运行结果:
在这里插入图片描述
VSCODE中运行结果(使用g++)

[Running] cd "c:\Code\C++\Test\test\" && g++ main.cpp -o main -std=c++17 && "c:\Code\C++\Test\test Main 2\"main
file: main.cpp line: 50 just test (www : 5)(sdf : 3.2)(ABC : aasdas)
file: main.cpp line: 51 just test2222 

[Done] exited with code=0 in 4.636 seconds

可以实现g++、msvc中一份代码相同结果。

进阶,以上代码还是不是最优的,会使生成的程序体积过大,因为生成模板实例过多,重复代码更多,同时代码不够简洁,最优代码如下,使用c++17 折叠表达式(逗号的妙用)以及stringstream将参数转为字符串列表,和参数名一样处理

同样的,使用方法不变,也就是不改变main函数,核心代码为:(变动部分见注释)

void testLog(const char* file, int lineno, const string &nameStr, const string &argStr)
{
    regex re { "\\s{0,},\\s{0,}" };
    vector<string> nameList = vector<string> { 
    	sregex_token_iterator(nameStr.begin(), nameStr.end(), re, -1), sregex_token_iterator() 
    };
    vector<string> argList = vector<string> { 
    	sregex_token_iterator(argStr.begin(), argStr.end(), re, -1), sregex_token_iterator() 
    };

    cout << "file: " << file << " line: " << lineno << " " << argList[0] << " ";
    for (size_t i = 1; i < nameList.size(); i++) {
    	//  已经获取到参数名和参数值的vector列表,可以任意diy输出格式了,此处使用(name : arg)
        cout << "(" << nameList[i] << " : " << argList[i] << ")";
    }
    cout << endl;
}

//  将参数输入到字符流中,用“,”隔开,然后再输出出来,参数名一样处理
template<typename ...Args>
string getArgs(Args && ...args)
{
    stringstream sstream;
    //  这里使用折叠表达式,括号部分(stream << args << ", ")实际可以理解成一个函数,args为参数
    //  “,...”即剩下的所有参数均执行括号部分args所执行的函数
    //  即所有参数以字符串形式输入到流中,并以逗号隔开,以便后续分割处理
	((sstream << args << ", "), ...);
    return sstream.str();
}

#define TEST_LOG(...) testLog(__FILE__, __LINE__, #__VA_ARGS__, getArgs(__VA_ARGS__));

执行结果如下:

file: main.cpp line: 38 just test (www : 5)(sdf : 3.2)(ABC : aasdas)
file: main.cpp line: 39 just test2222 

[Done] exited with code=0 in 4.609 seconds

项目中发现以下代码在某种极小概率下会引发regex库函数异常,于是自己写了个split函数。

    regex re { "\\s{0,},\\s{0,}" };
    vector<string> nameList = vector<string> { 
    	sregex_token_iterator(nameStr.begin(), nameStr.end(), re, -1), sregex_token_iterator() 
    };
    vector<string> argList = vector<string> { 
    	sregex_token_iterator(argStr.begin(), argStr.end(), re, -1), sregex_token_iterator() 
    };
void splitString(const string &pattern, string srcStr, vector<string> &result)
{
    int idx = srcStr.find(pattern);
    while (idx != -1) {
        string unitStr = srcStr.substr(0, idx); 
        result.push_back(unitStr);
        srcStr = srcStr.substr(idx + pattern.size());
        idx = srcStr.find(pattern);
    }
	if (!srcStr.empty()) {
		result.push_back(srcStr);
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值