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

TinyFormat 源码阅读(一)

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

惭愧很久没有更新B站了,博客也很少写,随便开个坑吧,一个阅读源码的系列,主要找一些质量不错的小型项目/库,也同时学习一下。


首先看一下项目简介:

tinyformat.h is a type safe printf replacement library in a single C++ header file. If you've ever wanted printf("%s", s) to just work regardless of the type of s, tinyformat might be for you.

tinyformat.h 是一个类型安全的 header only(只包含单个头文件) 的 printf 替换库。如果你曾经想过 printf("%s", s) 可以在无论 s 的类型如何的情况下都能正常工作,那么 tinyformat 可能就是你要的。

我们可以首先看一下 CMakeLists.txt 来决定我们阅读源码的顺序:

tinyformat 是一个 header only 的库也就是只包含头文件,使用时不需要链接而只需要 include 的库,因此我们在 CMakeLists.txt 中看不到一个库相关的 target ,但是可以先通过项目中的测试用例也就是 tinyformat_test 来对整个库建立一个直观的印象。

因此我们首先阅读 tinyformat_test.cpp 文件,没有使用测试框架,而是自己定义了一些宏作为辅助:

从名字能看出这个宏的目的是期待在测试中出现错误,整个执行块被包含在 try/catch 中,首先执行传入的 expression 语句,如果执行过程中存在异常抛出,那么接下来的代码不会被执行到,直接被捕获并忽略,如果没有抛出异常那么会触发 ++nfailed 表示测试结果与行为不符,并在 log 中打印失败的测试用例。不过这里只对 std::runtime_error 类型的异常做了处理算是一个小瑕疵。


这个也比较简单,如果 a 不等于 b 就会触发 ++nfailed ,不过这里有一些小细节要说一下,第一个是之所以这里把参数都用括号括起来是一个好的习惯,有助于我们约束宏的行为防止一些意料之外的错误,第二个是即使这样错误还是不可避免,比如这里如果我们以下面的两种方式使用 CHECK_EQUAL 宏:

我们就会发现 aCHECK_EQUAL 中自增的次数其实是不一致的,这很容易导致我们无法预测宏的行为,这也是为什么越来越多的人不推荐使用宏的原因:

甚至可以看到 3 != 3 这样的神奇现象,如果在一个复杂项目中出现这种日志会让我们非常头疼


从名字中的 Wrap 以及注释能看出这是使用 tfm::format 来包装和创建一些东西,结合用例:

可以看出用法还是比较简单的,本身库的作用就是替换 printf ,因此传入 std::fmt 的后几个参数 "asdf" 等都被拼到 someformat %s:%d:%d 中,最后通过 m_oss.str() 一并返回到 CHECK_EQUAL 中和 "10: someformat asdf:2:4" 进行比较,那结果应该是正确的。

接着看一下实现, 首先能看到 TestWrap::error 这个模板函数不是直接定义的,而是由 MAKE_ERROR_FUNC 通过 TINYFORMAT_FOREACH_ARGNUM 生成,前者很简单就是一个包含实现的宏,我们主要看一下后者,在项目头文件中找到的定义如下:

这个写法现在看来很丑很难理解,实际上考虑到这是一个基于 c++11 且多年没有更新的项目是正常的,因为当时的 c++ 没有对变长模板参数的支持,因此这种写法甚至在旧版本的 boost 实现中也有用到。回到 TINYFORMAT_FOREACH_ARGNUM 的定义,实际就是实例化出传入的函数参数 m 也就是 MAKE_ERROR_FUNC 的 16 个版本,因此我们需要再次回到 MAKE_ERROR_FUNC 的实现中。

可以看到对 n 的多次使用,首先是 TINYFORMAT_ARGTYPES(n) ,刚刚说了这种是用来代替变长模板参数,于是我们直接找一下定义应该也是类似的:

没错就是这么丑,通过 ## 连接 TINYFORMAT_ARGTYPES_ 和模板参数长度生成宏的名字并返回对应长度的模板参数,以 template<TINYFORMAT_ARGTYPES(2)> 为例返回的就是 template<class T1, class T2> ,其他两个宏 TINYFORMAT_VARARGSTINYFORMAT_PASSARGS 实现也基本相同这里不再赘述。


那实现中宏的部分和上面的类似,也是生成了不同参数长度版本的构造函数。这里 TestExceptionDef 是一个自定义的异常类型,继承自 std::runtime_error ,构造时通过 tfm::format 传入解释异常原因的提示信息。

后续的话就是一些测试用例了,那么对 tinyformat_test.cpp 测试代码部分的阅读我们就到此为止,下一篇我们将会正式从 tfm::format 函数开始窥探整个库的运行机制,感谢大家的阅读,再会!

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

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