惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

遥远的街市

状态机与函数式编程(二) - 遥远的街市 状态机与函数式编程 - 遥远的街市 筛选、编辑与网络社区 - 遥远的街市 谈谈我最近的编程语言选择 - 遥远的街市 我的金融投资书单 - 遥远的街市 谈 xxxholic - 遥远的街市 对 cursor.so 的两个简单测试 - 遥远的街市 内存盘简介 - 遥远的街市 网络文章存档工具 get-article 发布 - 遥远的街市
简单实现 C++ 字符串格式化 - 遥远的街市
henix · 2024-04-27 · via 遥远的街市

最后更新日期:2024-04-28

  字符串格式化是很常见的功能,传统上,我们使用 C 语言的 printf 来格式化。但作为一位 C++ 爱好者,printf 的缺点也很明显:

  1. 非类型安全
  2. 无法添加自定义类型

  std::cout 的问题在于:

  1. 进制和 padding 是通过设置流的全局状态实现的
  2. std::ostringstream 的 str() 方法会复制底层的 buffer ,不够高效
  3. 无法将结果追加到一个现有的字符串上,只能新建字符串再合并,会多一次拷贝

  流行的新一代格式化库如 fmt 的问题在于:

  1. 基于格式串的替换,实现比较繁杂

  市面上其他的 C++ 格式化库也无法让我满意,于是我在自己的 C++ 通用库 xlib 中实现了一个简单的 fmt 模块,使用方法如下:

BasicFormat fmt;

// pad 可实现用 0 补齐
fmt("pi = ", fmt.pad(4, 314), ' '); // => "pi = 0314 "

  可以通过继承来添加自定义类型:

struct Date {
    int year, month, day;
};

struct MyFormat: public BasicFormat {
    using BasicFormat::append;
    static void append(const Date& d, StringPusher& push) {
        append_all(push, d.year, '-', pad(2, d.month), '-', pad(2, d.day));
    }
    X_FMT_IMPLEMENT_FORMAT
};

MyFormat fmt;

fmt(Date { 2012, 4, 1 }); // => "2012-04-01"

  支持自定义类型的方法有这么几种:

  1. 在全局命名空间定义 operator<< ,std::cout 就用的这种
  2. 模板偏特化 + 后期 namespace 写入
  3. 继承

  我一开始用的是模板偏特化的方法,但这种方案的问题是,相关定义是全局的。所以对某一个自定义类型,同一个程序里只能定义一种格式化方法。而且 C++ 的 namespace 是开放的:即后面 include 进来的文件可以往任意 namespace 添加东西,我认为这样太动态。

  我后来改为用继承实现,因为:

  1. 对某一个类型的格式化方法是定义在类上的,同一个程序里可以定义多个不同的类,从而实现对同一类型的多种不同的格式化方法
  2. 类定义好后就不能往里面添加东西了,更有利于程序阅读和分析

附:fmt 的简化实现

class StringPusher {
    std::string& str;
public:
    StringPusher(std::string& str): str(str) {}
    void operator()(char c) {
        str.push_back(c);
    }
    void operator()(std::string_view buf) {
        str.append(buf.data(), buf.size());
    }
};

// 待格式化整数
template<class Int>
struct FormattedInt {
    Int n;
    uint8_t radix; // 进制
    CharCase charCase; // 大小写
    uint8_t padTo; // 补齐位数,0 表示不补齐
};

/**
 * https://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream
 */
#define X_FMT_IMPLEMENT_FORMAT template<class... Args>\
static void append_all(StringPusher& push, Args&&... args) {\
    using _expander = int[];\
    (void)_expander{ (append(std::forward<Args>(args), push), 0)... };\
}\
template<class... Args>\
std::string operator()(Args&&... args) {\
    std::string res;\
    StringPusher push(res);\
    append_all(push, args...);\
    return res;\
}

struct BasicFormat {
    // 字符串
    static void append(char c, StringPusher& push) {
        push(c);
    }
    static void append(const char* s, StringPusher& push) {
        push(s);
    }
    static void append(std::string_view s, StringPusher& push) {
        push(s);
    }
    static void append(const std::string& s, StringPusher& push) {
        push(s);
    }
    // 整数
    template<class Int, typename std::enable_if_t<std::is_integral_v<Int>>* = nullptr>
    static void append(Int n, StringPusher& push) {
        x::ser::formatInt(n, 10, CharCase::Lower, 0, push); // 省略 formatInt 定义
    }
    template<class Int>
    static FormattedInt<Int> pad(uint8_t n, Int x) {
        return FormattedInt<Int> { x, 10, CharCase::Lower, n };
    }
    template<class Int>
    static void append(FormattedInt<Int> fn, StringPusher& push) {
        x::ser::formatInt(fn.n, fn.radix, fn.charCase, fn.padTo, push); // 省略 formatInt 定义
    }
    X_FMT_IMPLEMENT_FORMAT
};