基于C++可变参数模板格式化字符串

栏目: C++ · 发布时间: 5年前

内容简介:遨游于C++世界时,最讨厌的当属于对c-style的兼容 。在格式化字符串时,通常使用的是

遨游于C++世界时,最讨厌的当属于对c-style的兼容 。

在格式化字符串时,通常使用的是 snprintf 这个c函数。 snprintfsprintf 的安全版,能够避免缓冲区溢出。

char buf[1024] = {0};
std::string s = "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

template<typename... Args>
string fmt(const char *format, Args&&... args) {
  char buf[128] = {0};
  snprintf(buf, sizeof(buf), format, convert(std::forward<Args>(args))...);
  return buf;
}

这是一个可变参数模板,args表示传入的参数们。我们的思路是将传入的参数做一个转换之后传给 snprintf

为了原封不动的保持左右值引用,首先是用 Args&& 代替 Args 的参数类型,此处模板函数的Args需要编译器推导,所以是一个通用引用(Universal reference),可以指代左值或右值。用 std::forward<Args> 能保持参数的左右值性质,做到参数的完美转发。

自动参数转换

convert 这个函数中,我们要将特定的类型转换成 const char* 类型,而那些能被 snprintf 接受的类型如 int , double , char* ,则原封不动的返回。

convert 函数针对不同的参数类型需要返回不同的类型。这里也将返回值作为一个模板类型即可。

template<typename T>
struct item_return {
  using type = T&&;
};

convert 函数的定义为:

template<typename T>
inline typename item_return<T>::type convert(T&& arg) {
  return static_cast<T&&>(arg);
}

convert函数默认将传入的参数原封不动的返回。接下来我们要做模板的偏特化,对于指定的对象,将其转换为const char *类型

// lvalue
template<>
struct item_return<obj&> {
  using type = const char*;
};

template<>
inline typename item_return<obj&>::type convert<obj&>(obj &arg) {
  std::cout << "receive lvalue\n";
  return arg.s.c_str();
}

// rvalue
template<>
struct item_return<obj> {
  using type = const char*;
};

template<>
inline typename item_return<obj>::type convert<obj>(obj &&arg) {
  std::cout << "receive rvalue\n";
  return arg.s.c_str();
}

注意,返回值也是需要偏特化的。

最后

我构造了一个class,hook他的两个构造函数以便于观察是否发生了拷贝。

class obj {
public:
  string s;
  obj(const char * ss) {
    s = ss;
  }
  obj(const obj& other):s(other.s) {
    printf("copy constructor\n");
  }
  obj(obj&& other):s(other.s) {
    printf("move constructor\n");
    other.s.clear();
  }
};

之后我们使用fmt函数,就能像格式化c-style字符串一样,格式化任意一个对象啦。

int main() {
  obj a("haha");
  int b = 3;
  std::cout << fmt("%s %s\n%d %d", a, obj("xixi"), b, 2) << std::endl;
  return 0;
}

运行结果为

receive lvalue
receive rvalue
haha xixi
3 2

很好,并没有发生拷贝。

参考资料


以上所述就是小编给大家介绍的《基于C++可变参数模板格式化字符串》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beautiful Code

Beautiful Code

Greg Wilson、Andy Oram / O'Reilly Media / 2007-7-6 / GBP 35.99

In this unique work, leading computer scientists discuss how they found unusual, carefully designed solutions to difficult problems. This book lets the reader look over the shoulder of major coding an......一起来看看 《Beautiful Code》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具