资讯动态

BusTub 中的 {fmt} 格式化库 API 实战指南:从基础格式化到用户自定义类型扩展

发布时间:2026/10/15 11:40:37 来源:尧图企业网站定制
数据库教育后端【免费下载链接】bustubThe BusTub Relational Database Management System (Educational)项目地址https://gitcode.com/gh_mirrors/bu/bustub点击查看免费下载{fmt} 是一个开源的高性能、类型安全的 C 格式化库本篇文章以 BusTub 数据库管理系统仓库third_party/fmt/doc/api.md中的 API 参考文档为核心系统梳理其全部 API 组件、格式化函数、自定义类型扩展机制与编译期检查能力并结合 BusTub 源码中的真实集成方式与调用实践进行纵深讲解。读完本文你将掌握 {fmt} 从fmt::print到formatter特化、fmt::join、FMT_COMPILE等进阶用法的完整知识并能在自己的 C 项目中直接复用这些实战模式。一、{fmt} 在 BusTub 中的角色与 API 组成总览BusTub 是卡内基梅隆大学数据库组维护的教学型关系数据库管理系统Relational Database Management System其源码在 binder、executor、catalog 等模块中广泛依赖 {fmt} 完成字符串拼接、计划节点PlanNode描述、错误信息格式化等工作。{fmt} 以第三方库形式随仓库一起构建顶层 CMakeLists.txt 将third_party/fmt/include加入全局头文件搜索路径third_party/CMakeLists.txt 通过add_subdirectory(fmt)将其纳入构建体系源码中大量使用fmt::format、fmt::print、fmt::join例如 src/execution/fmt_impl.cpp、src/binder/fmt_impl.cpp 等。{fmt} 的 API 由以下组件构成全部函数与类型位于fmt命名空间宏统一以FMT_为前缀头文件职责fmt/base.h基础 API面向char/UTF-8 的核心格式化函数自带 C20 编译期检查依赖最小、编译更快fmt/format.h完整格式化 APIfmt::format等函数以及 locale 支持fmt/ranges.h范围range与元组tuple的格式化fmt/chrono.h日期与时间格式化fmt/std.h标准库类型的 formatter 特化fmt/compile.h格式字符串编译编译期生成格式化代码fmt/color.h终端颜色与文本样式输出fmt/os.h系统 APIstd::ostream输出、Windows 错误处理等fmt/ostream.hstd::ostream流式输出支持fmt/args.h动态参数列表fmt/printf.h类型安全的printffmt/xchar.h可选的wchar_t宽字符支持说明fmt/base.h只有在将 {fmt} 作为库而非 header-only 模式使用时才能体现其编译期收益该头文件默认已为int、long long、unsigned、unsigned long long、float、double、long double、bool、char、const char*、fmt::string_view、const void*等类型提供formatter特化。二、Base API核心格式化函数格式化函数采用与 Pythonstr.format相似的格式字符串语法fmt是包含字面文本与{}花括号占位字段的格式字符串占位字段在结果字符串中被替换为格式化后的参数args是待格式化对象组成的参数列表。I/O 错误默认以std::system_error异常形式报告。2.1 直接输出print与println// 输出到标准输出 fmt::print(format_stringT..., T... args); fmt::println(format_stringT..., T... args); // 自动追加换行 // 输出到指定文件流 fmt::print(FILE* f, format_stringT..., T... args); fmt::println(FILE* f, format_stringT..., T... args);其底层实现是vprint/vprintln声明见 third_party/fmt/include/fmt/base.h。println相比print会在末尾自动补一个换行符。2.2 写入迭代器format_to、format_to_n与formatted_size// 将格式化结果写入任意输出迭代器如 std::back_inserter返回迭代器 auto it fmt::format_to(OutputIt out, format_stringT..., T... args); // 限定最多写入 n 个字符返回 format_to_n_result{out, size} auto result fmt::format_to_n(OutputIt out, size_t n, format_stringT..., T... args); // result.out 为写入末尾后的迭代器result.size 为总字符数未截断 // 仅计算格式化结果占用的字节数不实际写入 size_t sz fmt::formatted_size(format_stringT..., T... args);format_to_n适合需要受控写入的场景如固定大小缓冲区formatted_size常用于预先分配缓冲区大小。2.3 类型安全的format_string与运行时字符串fmt::runtimefmt::format_stringT...是格式字符串类型可从字符串字面量或constexpr字符串隐式构造并在 C20 下于编译期完成格式串与参数类型的匹配检查。若格式字符串只能在运行时获得则必须用fmt::runtime显式包装以绕过编译期检查std::string s fmt::format(fmt::runtime(runtime_fmt), 42);2.4 BusTub 中的基础 API 实践BusTub 各模块的ToString()实现是fmt::format的典型用法例如 src/binder/fmt_impl.cpp 中绑定函数调用的描述auto BoundFuncCall::ToString() const - std::string { return fmt::format({}({}), func_name_, args_); }src/execution/fmt_impl.cpp 中聚合计划节点的描述同样借助fmt::format一次拼接多个字段auto AggregationPlanNode::PlanNodeToString() const - std::string { return fmt::format(Agg {{ types{}, aggregates{}, group_by{} }}, agg_types_, aggregates_, group_bys_); }注意其中{{与}}是花括号的转义写法这是 {fmt} 格式字符串语法的重要细节。三、格式化用户自定义类型format_as与formatter特化{fmt} 已为大量标准 C 类型提供 formatter范围/元组见fmt/ranges.h日期时间见fmt/chrono.h其余标准库类型见fmt/std.h。让你的自定义类型可被格式化有两种途径3.1 轻量方案format_as函数如果自定义类型希望“借用”另一类型的格式说明符进行格式化只需在与该类型相同的命名空间内提供一个format_as函数返回一个可格式化类型的对象#include fmt/format.h namespace kevin_namespacy { enum class film { house_of_cards, american_beauty, se7en 7 }; auto format_as(film f) { return fmt::underlying(f); } } int main() { fmt::print({}\n, kevin_namespacy::film::se7en); // Output: 7 }3.2 完整方案特化formatter结构体模板特化formatter能获得对解析与格式化流程的完全控制需要实现两个方法template struct fmt::formatterT { // 解析格式说明符并存入 formatter。 // [ctx.begin(), ctx.end()) 是待解析的说明符字符区间 // 例如 fmt::format({:f} continued, ...) 中区间内容为 f} continued // 解析到 } 或区间末尾为止返回指向 } 的迭代器。 constexpr auto parse(format_parse_context ctx) - format_parse_context::iterator; // 依据已解析的格式说明对 value 进行格式化并写入 ctx.out()。 auto format(const T value, format_context ctx) const - format_context::iterator; };推荐做法是通过继承或组合复用已有 formatter这样无需自行实现标准格式说明符。例如让color枚举复用string_view的格式化能力// color.h: #include fmt/base.h enum class color {red, green, blue}; template struct fmt::formattercolor: formatterstring_view { // parse 继承自 formatterstring_view auto format(color c, format_context ctx) const - format_context::iterator; };// color.cc: #include color.h #include fmt/format.h auto fmt::formattercolor::format(color c, format_context ctx) const - format_context::iterator { string_view name unknown; switch (c) { case color::red: name red; break; case color::green: name green; break; case color::blue: name blue; break; } return formatterstring_view::format(name, ctx); }由于formatterstring_view::format定义在fmt/format.h中源文件必须包含该头而parse从基类继承因此自动识别全部字符串格式说明符。例如fmt::format({:10}, color::blue) // 返回 blue对同一类型同时提供formatter特化与format_as重载是不允许的。3.3 为类继承层次编写 formatter借助 SFINAE 可以为一个类层次统一编写 formatter子类对象通过基类指针格式化时也能输出正确的动态类型// demo.h: #include type_traits #include fmt/core.h struct A { virtual ~A() {} virtual std::string name() const { return A; } }; struct B : A { virtual std::string name() const { return B; } }; template typename T struct fmt::formatterT, std::enable_if_tstd::is_base_of_vA, T, char : fmt::formatterstd::string { auto format(const A a, format_context ctx) const { return formatterstd::string::format(a.name(), ctx); } };// demo.cc: #include demo.h #include fmt/format.h int main() { B b; A a b; fmt::print({}, a); // Output: B }3.4 BusTub 源码中的 formatter 特化实例BusTub 正是以继承方式为表达式体系定制 formatter 的典范。在 src/include/binder/bound_expression.h 中所有BoundExpression派生类与对应的std::unique_ptrT都通过继承fmt::formatterstd::string获得格式化能力格式化结果即ToString()的输出template typename T struct fmt::formatterT, std::enable_if_tstd::is_base_ofbustub::BoundExpression, T::value, char : fmt::formatterstd::string { template typename FormatCtx auto format(const T x, FormatCtx ctx) const { return fmt::formatterstd::string::format(x.ToString(), ctx); } };同一文件src/include/binder/bound_expression.h还为枚举类型ExpressionType提供了继承formatterstring_view的特化将枚举值映射为可读的名称字符串Constant、ColumnRef、AggregationCall等。这样fmt::format({}, expr_type)便能在不编写大量分支的情况下直接输出人类可读的类型名——这正是 3.2 节继承式 formatter 模式的工程级落地。四、编译期检查与类型擦除4.1 编译期格式串检查在支持 C20consteval的编译器上编译期格式字符串检查默认开启旧编译器可通过FMT_STRING宏获得等效能力要求 C14 或更高C11 下为空操作。未使用的参数与 Python 的str.format一样被允许。若希望强制使用FMT_STRING形式的旧式检查可定义预处理宏FMT_ENFORCE_COMPILE_STRING此时以普通字符串调用接受FMT_STRING的函数将编译失败。4.2 类型擦除自定义带编译期检查的格式化函数通过fmt::format_string、fmt::vformat与fmt::make_format_args组合可以编写自己的、带编译期检查且二进制体积更小的日志函数——vlog不对参数类型做模板化因此能改善编译时间并减小代码体积#include fmt/format.h void vlog(const char* file, int line, fmt::string_view fmt, fmt::format_args args) { fmt::print({}: {}: {}, file, line, fmt::vformat(fmt, args)); } template typename... T void log(const char* file, int line, fmt::format_stringT... fmt, T... args) { vlog(file, line, fmt, fmt::make_format_args(args...)); } #define MY_LOG(fmt, ...) log(__FILE__, __LINE__, fmt, __VA_ARGS__) MY_LOG(invalid squishiness: {}, 42);关键类型包括fmt::make_format_args(T...)打包参数、basic_format_args/format_args擦除后的参数列表、basic_format_arg单个擦除参数。此外fmt/base.h还提供basic_format_parse_context、context、format_context等上下文类型供 formatter 实现使用。五、命名参数与基础兼容类型fmt::arg(const Char*, const T)用于命名参数常用于本地化场景如fmt::format({name}, fmt::arg(name, world))。注意命名参数目前不参与编译期检查。兼容性方面fmt/base.h提供basic_string_view与string_view用于在不依赖std::string_view或其缺失的环境时完成字符串参数传递。六、Format API完整格式化功能与实用工具fmt/format.h定义完整的格式化 API在基础 API 之上增加格式化函数与 locale 支持。6.1 核心函数fmt::format(format_stringT..., T...) - std::string将参数格式化为字符串。其实现见 third_party/fmt/include/fmt/format.h是return vformat(fmt.str, vargsT...{{args...}});即所有参数先被擦除为统一表示后再进入vformatfmt::vformat(string_view, format_args) - std::string以已擦除参数列表进行格式化见 third_party/fmt/include/fmt/format.hoperator_a生成命名参数的字符串字面量操作符位于fmt::literals命名空间。6.2 实用工具函数函数作用fmt::ptr(T)格式化指针地址unique_ptr/shared_ptr版本位于fmt/std.hfmt::underlying(Enum)取枚举的底层整数值fmt::to_string(const T)用默认格式将任意可格式化对象转为std::string整型重载使用栈上缓冲区无动态分配见 third_party/fmt/include/fmt/format.hfmt::group_digits(T)按千位分组输出数字fmt::detail::buffer/fmt::basic_memory_buffer内存缓冲类型basic_memory_buffer可在栈上预分配inline_buffer_size字节6.3 系统错误处理{fmt}不使用errno向用户传递错误但它可能调用会设置errno的系统函数因此用户不应假设库函数调用后errno的值被保留。fmt::system_error用于构造携带系统错误码的异常fmt::format_system_error用于格式化系统错误消息。6.4 自定义分配器basic_memory_buffer的模板参数可指定自定义分配器从而让格式化过程使用自定义内存管理using custom_memory_buffer fmt::basic_memory_bufferchar, fmt::inline_buffer_size, custom_allocator;也可编写使用自定义分配器的格式化函数将custom_memory_buffer与fmt::vformat_to(std::back_inserter(buf), fmt, args)配合最后取出buf.data()与buf.size()构造自定义分配器管理的字符串。分配器仅用于输出容器内置类型与字符串类型的格式化通常不会产生堆分配仅非默认浮点格式化偶尔回退到sprintf。6.5 Locale 支持默认所有格式化与 locale 无关。要插入 locale 的数字分隔符使用L格式说明符#include fmt/core.h #include locale std::locale::global(std::locale(en_US.UTF-8)); auto s fmt::format({:L}, 1000000); // s 1,000,000fmt/format.h还提供接受std::locale参数的format、format_to、formatted_size重载。locale 类型作为模板参数传递以避免引入昂贵的locale头文件。七、范围与元组格式化fmt/ranges.hfmt/ranges.h为范围range与元组提供格式化支持包括std::vector等标准容器#include fmt/ranges.h fmt::print({}, std::tuplechar, int{a, 42}); // Output: (a, 42)fmt::join用自定义分隔符连接元素其重载包括fmt::join(Range, string_view)连接整个范围fmt::join(It, Sentinel, string_view)连接迭代器区间[It, Sentinel)fmt::join(std::initializer_listT, string_view)连接初始化列表。#include fmt/ranges.h auto t std::tupleint, char{1, a}; fmt::print({}, fmt::join(t, , )); // Output: 1, aBusTub 实践在 src/execution/fmt_impl.cpp 的AbstractPlanNode::ChildrenToString中子计划节点的多行文本先被逐行缩进再通过fmt::join(children_str, \n)以换行符连接成整体配合fmt::format(\n{}, ...)输出——这是fmt::join在真实工程中处理多行文本拼接的典型用法return fmt::format(\n{}, fmt::join(children_str, \n));八、日期与时间格式化fmt/chrono.hfmt/chrono.h为以下类型提供 formatterstd::chrono::durationstd::chrono::time_pointstd::tm格式语法详见 Chrono 格式说明。示例#include fmt/chrono.h int main() { std::time_t t std::time(nullptr); fmt::print(The date is {:%Y-%m-%d}., fmt::localtime(t)); // Output: The date is 2020-11-07. // (2020-11-07 替换为当前日期) using namespace std::literals::chrono_literals; fmt::print(Default format: {} {}\n, 42s, 100ms); // Output: Default format: 42s 100ms fmt::print(strftime-like format: {:%H:%M:%S}\n, 3h 15min 30s); // Output: strftime-like format: 03:15:30 }辅助函数fmt::localtime(std::time_t)与fmt::gmtime(std::time_t)用于将时间戳转换为std::tm分别为本地时间与 UTC 时间。九、标准库类型格式化fmt/std.hfmt/std.h为以下标准库类型提供 formatterstd::atomic、std::atomic_flagstd::bitsetstd::error_codestd::exceptionstd::filesystem::pathstd::monostatestd::optionalstd::source_locationstd::thread::idstd::variant并提供了智能指针重载fmt::ptr(const std::unique_ptrT, Deleter)与fmt::ptr(const std::shared_ptrT)。关于std::variant只有当每一个 variant 备选类型都可格式化时std::variant才可被格式化且要求编译环境具备__cpp_lib_variant特性宏。示例#include fmt/std.h fmt::print({}, std::variantchar, float(x)); // Output: variant(x) fmt::print({}, std::variantstd::monostate, char()); // Output: variant(monostate)位域与 packed 结构体的注意事项要格式化位域成员或带有__attribute__((packed))的结构体字段需先通过类型转换或一元转换为底层或兼容类型struct smol { int bit : 1; }; auto s smol(); fmt::print({}, s.bit);这是 C“完美转发”机制的已知限制使用时应留意。十、格式字符串编译fmt/compile.hfmt/compile.h通过FMT_COMPILE宏或fmt::literals命名空间中的_cf用户自定义字面量提供格式字符串编译与编译期constexpr格式化。被FMT_COMPILE或_cf标记的格式字符串会在编译期完成解析、检查并转换为高效的格式化代码。该机制支持内置类型、字符串类型以及format函数将格式上下文类型作为模板参数的用户自定义类型template struct fmt::formatterpoint { constexpr auto parse(format_parse_context ctx); template typename FormatContext auto format(const point p, FormatContext ctx) const; };需要注意格式字符串编译可能产生比默认 API 更多的二进制代码只推荐在格式化成为性能瓶颈的热点位置使用。十一、终端颜色与文本样式fmt/color.hfmt/color.h支持终端颜色与文本样式输出fmt::print(const text_style, format_stringT..., T...)带样式打印fmt::fg(detail::color_type)设置前景色fmt::bg(detail::color_type)设置背景色fmt::styled(const T, text_style)为值附加样式。例如fmt::print(fmt::fg(fmt::color::red), danger: {}, 1)即可在支持 ANSI 颜色的终端上输出红色文本。十二、std::ostream支持fmt/ostream.hfmt/ostream.h提供std::ostream支持包括格式化那些重载了插入操作符operator的用户自定义类型。要让某类型通过std::ostream可格式化只需提供继承自ostream_formatter的 formatter 特化#include fmt/ostream.h struct date { int year, month, day; friend std::ostream operator(std::ostream os, const date d) { return os d.year - d.month - d.day; } }; template struct fmt::formatterdate : ostream_formatter {}; std::string s fmt::format(The date is {}, date{2012, 12, 9}); // s The date is 2012-12-9相关工具fmt::streamed(const T)强制以operator方式格式化对象fmt::print(std::ostream, format_stringT..., T...)将格式化结果写入任意std::ostream。十三、动态参数列表fmt/args.hfmt/args.h提供dynamic_format_arg_store——一种构建者风格builder-like的 API用于动态构造格式参数列表。当参数集合在运行时才能确定如参数个数或类型不确定的解释器、脚本绑定场景时非常有用#include fmt/args.h fmt::dynamic_format_arg_storefmt::format_context store; store.push_back(42); store.push_back(answer); fmt::vprint({} {}, store);十四、安全的printffmt/printf.hfmt/printf.h提供printf风格的格式化采用 printf 格式字符串语法并支持 POSIX 位置参数扩展。与标准printf不同fmt 版本完全类型安全当参数类型与格式说明不匹配时会抛出异常而不会产生未定义行为。fmt::printf(string_view fmt, const T... args); fmt::fprintf(std::FILE* f, const S fmt, const T... args); fmt::sprintf(const S fmt, const T... args); // 返回 std::string十五、宽字符串支持fmt/xchar.h可选的fmt/xchar.h提供wchar_t与异种字符类型支持包括fmt::is_char判断字符类型的特征类、fmt::wstring_view、fmt::wformat_context与fmt::to_wstring(const T)。十六、与 C20std::format的兼容性{fmt} 几乎完整实现了 C20 格式化库std::format仅存在以下差异所有名称定义在fmt命名空间而非std中以避免与标准库实现发生冲突宽度计算不使用 grapheme clusterization字素簇切分——该功能已在独立分支中实现但尚未合入主分支。这意味着 {fmt} 用户可以在 C20 标准库支持不完整的环境中提前获得std::format的等价能力或作为标准库实现的对比与后备方案。十七、在 BusTub 中的集成与工程实践总结回顾 BusTub 对 {fmt} 的集成方式可以提炼出三个可直接复用的工程模式作为第三方子项目构建在 third_party/CMakeLists.txt 中add_subdirectory(fmt)并将 third_party/fmt/include 加入 include 路径CMakeLists.txt即可全局使用而无需额外链接配置统一为对象体系定制 formatter在 src/include/binder/bound_expression.h 中通过继承fmt::formatterstd::string并以 SFINAE 约束基类为整个BoundExpression派生体系一次性提供格式化能力格式化结果与ToString()保持一致——这让fmt::format({}, expr)与fmt::join(exprs, , )可以直接用于日志与调试输出面向可读性输出计划树在 src/execution/fmt_impl.cpp 中组合fmt::format与fmt::join生成多行缩进的执行计划描述展示了占位符转义{{/}}、容器格式化与join分隔符在真实数据库系统中的综合运用。{fmt} 的完整格式字符串语法对齐、宽度、填充、精度、进制、Llocale 说明符等见仓库内文档 third_party/fmt/doc/syntax.md与本文的 API 参考互为补充。掌握本文所述的 API 分层结构与自定义类型扩展机制即可在 BusTub 或任何 C 项目中写出类型安全、可读且高性能的字符串格式化代码。赞分享数据库教育后端【免费下载链接】bustubThe BusTub Relational Database Management System (Educational)项目地址https://gitcode.com/gh_mirrors/bu/bustub点击查看免费下载相关推荐告别格式化困境fmt库自定义类型扩展完全指南告别格式化困境fmt库自定义类型扩展完全指南 你是否还在为C中自定义类型的格式化而烦恼尝试过各种方法却依然无法优雅地输出用户定义类型本文将带你一步掌握标准库react-native-windows 内置 {fmt} 库 API 完全参考格式化、日志与自定义类型react native windows 内置 {fmt} 库 API 完全参考格式化、日志与自定义类型 导读 本篇文章以 react native wind跨平台前端彻底解决fmtlib/fmt中用户自定义类型与编译时格式化(_cf)的兼容难题彻底解决fmtlib/fmt中用户自定义类型与编译时格式化 _cf 的兼容难题 fmtlib/fmt作为一款现代格式化库以其高效、安全和易用性受到广大开发者标准库上一篇使用 List.Item.Meta 快速构建 Arco Design 基础列表头像、标题与描述的组合实战下一篇FlashList 仓库的 Agent 反馈闭环用 analyze-feedback Skill 自动化沉淀 GitHub Actions 实战经验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑