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

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

内容简介:遨游于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++可变参数模板格式化字符串》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

颠覆暴利

颠覆暴利

曾德超、张志前 / 2015-4-1 / 58

本书从金融人士的视角研究互联网时代的金融创新,全面系统地介绍了我国互联网金融的产生背景及原因,梳理了互联网金融的基本理论,分析了互联网金融的各种业态和运营模式,介绍了互联网企业和金融企业的应对策略。本书还对互联网金融存在的风险和监管进行了研究,对互联网金融未来的发展前景进行了展望。本书是学习研究互联网金融不可多得的一本参考书。一起来看看 《颠覆暴利》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器