欢迎光临散文网 会员登陆 & 注册

TinyFormat 源码阅读(三)

2022-09-08 00:21 作者:Meriex  | 我要投稿

回顾一下昨天的部分我们读到了 formatImpl 的实现, printFormatStringLiteral 函数找到字符串格式化字符前的部分并写入流,返回剩余的部分。

接着我们看一下 streamStateFromFormat 的实现:

我们可以看到函数的主要功能就是解析诸如 %04d 这样的格式化字符并为 ostream 设置输出格式,这里遵循的是 C99 标准中的规范:

具体的就不多展开了,有兴趣可以自己了解一下,这里我们专注语言上的一些风格和技巧。

回到 formatImpl 函数中,一部分的流格式设置和 arg 填充在 streamStateFromFormat 中完成了,而另一部分则通过 arg.format 完成:

这里可以看到通过模板和宏配合声明并定义了四个 formatValue 函数:

因此对于 char/signed char/unsigned char 这三种类型优先调用非模板版本也就是直接向流中写入数据,而对于其他的版本则调用模板版本做进一步的处理。

在模板版本的 formatValue 中对传入的 value 分四种情况进行处理:

  1. 能隐式转化成 char 并且格式化字符也确实是 c 的,通过 formatValueAsType::invoke 调用 static_cast 转成 char 类型并写入流

  2. 能隐式转为 const void* 且格式化字符为 p 的,同样转化后写入流

  3. 需要截断的,只写入截断前部分到流中

  4. 其他情况的直接通过 << vaule 写入,依赖类型自身支持

以上过程又被 formatImpl 在一个 while 循环中重复,就这样完成了字符串格式化的整个过程并由 oss.str() 返回。

到这里的话 TinyFormat 库的基本逻辑我们都说完了,接下来说说关于变长参数部分的拓展,之前在第一节分析 tinyformat_test.cpp 的时候我们分析了 TINYFORMAT_ARGTYPESTINYFORMAT_FOREACH_ARGNUM  等宏的实现,以及如何依靠这些宏完成对变长参数的支持,那么在 c++11 加入了对变长参数的支持后,我们又该怎样实现这些函数呢。

FormatListN 的构造函数为例, c++98 版本的实现如下 :

简单回顾一下,这里使用一个空的 init 函数作为中止函数,接着借助宏实例化出最多支持 16 个参数的构造函数和 init 函数,尝试初始化时由对应参数数量版本的构造函数从下标 i=0 开始调用 init 函数,逐次递增下标并去除已赋值的参数直到所有参数全部初始化完成。

这是 c++11 支持变长参数的版本:

可以看到这里通过 template<typename ... Args> 声明 FormatListN 的构造函数是一个模板函数,并且接受变长模板参数。同时很精巧的令 m_formatterStore 是一个 FormatArg 类型的数组,且其构造函数可以接收任意类型 T ,还通过 FormatListN 的模板参数 template <size_t N> 来保证传入的模板参数个数和数组长度一直,因此可以直接通过 m_formatterStore { FormatArg(args)... } 把模板参数一次性复制给数组,非常有趣的一种写法,否则的话一般来说即使是变长模板参数也需要通过递归的方式完成赋值( c++17 可以用括号展开, c++11 用初始化列表魔法)。

特别注意由于这里使用内置数组,因此我们要通过偏特化的方式对长度为零的 FormatListN 进行处理:

最后再来看一下通过 makeFormatList 构造参数的部分,同样是 c++11 实现的版本:

可以看到这里借助了 sizeof...(Args) 拿到了 FormatListN 所需要的模板参数 N ,并传入所有参数用以构建和初始化参数列表。

好了,那么对 TinyFormat 库的整个阅读到这基本上就结束了,从这个简小但功能强大的库中我们收获了一些常见的宏语法、对变长模板参数的使用以及一个非常精妙的利用变长模板参数向数组复制的小技巧。

下次再会!


TinyFormat 源码阅读(三)的评论 (共 条)

分享到微博请遵守国家法律