TinyFormat 源码阅读(三)
回顾一下昨天的部分我们读到了 formatImpl
的实现, printFormatStringLiteral
函数找到字符串格式化字符前的部分并写入流,返回剩余的部分。
接着我们看一下 streamStateFromFormat
的实现:
我们可以看到函数的主要功能就是解析诸如 %04d
这样的格式化字符并为 ostream
设置输出格式,这里遵循的是 C99
标准中的规范:
具体的就不多展开了,有兴趣可以自己了解一下,这里我们专注语言上的一些风格和技巧。
回到 formatImpl
函数中,一部分的流格式设置和 arg
填充在 streamStateFromFormat
中完成了,而另一部分则通过 arg.format
完成:
这里可以看到通过模板和宏配合声明并定义了四个 formatValue
函数:
因此对于 char/signed char/unsigned char
这三种类型优先调用非模板版本也就是直接向流中写入数据,而对于其他的版本则调用模板版本做进一步的处理。
在模板版本的 formatValue
中对传入的 value
分四种情况进行处理:
能隐式转化成
char
并且格式化字符也确实是c
的,通过formatValueAsType::invoke
调用static_cast
转成char
类型并写入流能隐式转为
const void*
且格式化字符为p
的,同样转化后写入流需要截断的,只写入截断前部分到流中
其他情况的直接通过
<< vaule
写入,依赖类型自身支持
以上过程又被 formatImpl
在一个 while
循环中重复,就这样完成了字符串格式化的整个过程并由 oss.str()
返回。
到这里的话 TinyFormat
库的基本逻辑我们都说完了,接下来说说关于变长参数部分的拓展,之前在第一节分析 tinyformat_test.cpp
的时候我们分析了 TINYFORMAT_ARGTYPES
和 TINYFORMAT_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
库的整个阅读到这基本上就结束了,从这个简小但功能强大的库中我们收获了一些常见的宏语法、对变长模板参数的使用以及一个非常精妙的利用变长模板参数向数组复制的小技巧。
下次再会!