遨游于 C++ 世界时, 最讨厌的当属于对 c-style 的兼容 .
在格式化字符串时, 通常使用的是 snprintf 这个 c 函数. snprintf 是 sprintf 的安全版, 能够避免缓冲区溢出.
charbuf[1024] = {0};std::strings ="Hello world";snprintf(buf,sizeof(buf),"format str: %s", s.c_str());
snprintf 接受的参数跟 printf 差不多, 都是 c-style 的数据类型, 如 %s 接受的是 const char* 类型的数据, 这就需要我们将 std::string 做一个转换. 这一个小小的转换让我觉得非常不爽, 有没有可能让 std::string 在做某个函数的参数时能自动做转换? 甚至让一个将一个普通的对象自动转换成 const char* 类型?
接下来我们将利用 c++11 的可变参数模板 (variadic templates), 参数包(parameters pack), 完美转发(perfect forwarding) 等特性来实现这一想法.
参数列表完美转发
写 C++ 代码时, 我满脑子都是怎么最大限度地提高性能. 我们这次的目标也一样, 在提供方便的同时, 不要对性能有太大影响, 甚至不影响.
首先是要将传入 fmt 函数的参数完美转发至 snprintf .
templatestringfmt(constchar*format, Args&&... args){charbuf[128] = {0};snprintf(buf,sizeof(buf), format, convert(std::forward(args))...);returnbuf;}
这是一个可变参数模板, args 表示传入的参数们. 我们的思路是将传入的参数做一个转换之后传给 snprintf .
为了原封不动的保持左右值引用, 首先是用 Args&& 代替 Args 的参数类型, 此处模板函数的 Args 需要编译器推导, 所以是一个通用引用(Universal reference), 可以指代左值或右值. 用 std::forward<Args> 能保持参数的左右值性质, 做到参数的完美转发.
自动参数转换
在 convert 这个函数中, 我们要将特定的类型转换成 const char* 类型, 而那些能被 snprintf 接受的类型如 int , double , char* , 则原封不动的返回.
convert 函数针对不同的参数类型需要返回不同的类型. 这里也将返回值作为一个模板类型即可.
templatestructitem_return {usingtype = T&&;};
convert 函数的定义为:
templateinlinetypenameitem_return::typeconvert(T&& arg){returnstatic_cast(arg);}
convert 函数默认将传入的参数原封不动的返回. 接下来我们要做模板的偏特化, 对于指定的对象, 将其转换为 const char * 类型
// lvaluetemplate<>structitem_return {usingtype =constchar*;};template<>inlinetypenameitem_return::type convert(obj &arg) {std::cout<<"receive lvalue\n";returnarg.s.c_str();}// rvaluetemplate<>structitem_return {usingtype =constchar*;};template<>inlinetypenameitem_return::type convert(obj &&arg) {std::cout<<"receive rvalue\n";returnarg.s.c_str();}
注意, 返回值也是需要偏特化的.
最后
我构造了一个 class,hook 他的两个构造函数以便于观察是否发生了拷贝.
classobj {public:strings; obj(constchar* ss) { s = ss; } obj(constobj& other):s(other.s) {printf("copy constructor\n"); } obj(obj&& other):s(other.s) {printf("move constructor\n"); other.s.clear(); }};
之后我们使用 fmt 函数, 就能像格式化 c-style 字符串一样, 格式化任意一个对象啦.
intmain(){obja("haha");intb =3;std::cout<< fmt("%s %s\n%d %d", a, obj("xixi"), b,2) <
运行结果为
receivelvaluereceivervaluehaha xixi32
很好, 并没有发生拷贝.
看我主页简介免费 C++ 学习资源, 视频教程, 职业规划, 面试详解, 学习路线, 开发工具
每晚 8 点直播讲解 C++ 编程技术.
来源: http://www.jianshu.com/p/f6b9b30ee556