You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确实现基于fmt的循环缓冲区格式化及文件刷新方案?

实现带固定缓冲区的fmt格式化文件类

我实现了一个带固定长度内部缓冲区的文件类,用来替代CRT文件。现在想给这个类加format方法,借助fmt库把内容格式化到内部缓冲区;缓冲区满了就刷到文件,然后从缓冲区开头继续格式化。目标是减少内存分配、复制和分支操作(比如push_back)来提升性能,同时保持和fmt::format一样的易用性。

之前我改了iterator_buffer的grow方法和capacity做了概念验证,但方式不够规范:

验证用修改代码

template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
 protected:
     FMT_CONSTEXPR20 void grow(size_t) override
     {
        std::cout << "FLUSH: " << std::string_view{&*this->begin(), &*this->end()} << '\n';
        buffer<T>::set(&*this->begin(), 32); buffer<T>::clear();
     }

 public:
  explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, 32) {}

  auto out() -> T* { return &*this->end(); }
};

测试主代码

std::array<char, 32> buffer; // 实际缓冲区会大很多

auto out = fmt::format_to(buffer.data(), "example format string 0x{:016X}. {} {} {} {} ", 0x99, "this could have some", "somewhat", "very long", "data in reality");

std::cout << "CLOSE: " << std::string_view{ buffer.data(), out } << '\n';

输出结果

FLUSH: example format string 0x00000000
FLUSH: 00000099. this could have some
FLUSH: somewhat very long
CLOSE: data in reality

现在想知道正确的实现方式是什么?


正确实现方案

1. 自定义fmt兼容的输出迭代器

不要直接修改fmt自带的iterator_buffer,标准做法是实现自定义输出迭代器,将缓冲区管理和文件刷新逻辑内聚其中,同时满足fmt对输出迭代器的要求。迭代器核心逻辑是写入字符时检查缓冲区状态,满了就自动刷新到文件,然后重置缓冲区指针。

实现代码:

class FileBufferOutputIterator {
public:
    using value_type = char;
    using difference_type = std::ptrdiff_t;
    using pointer = char*;
    using reference = char&;
    using iterator_category = std::output_iterator_tag;

    FileBufferOutputIterator(FILE* file, char* buffer, size_t buffer_size)
        : file_(file), buffer_(buffer), buffer_end_(buffer + buffer_size), current_(buffer) {}

    // 写入单个字符,缓冲区满则自动刷新
    FileBufferOutputIterator& operator=(char c) {
        if (current_ == buffer_end_) flush();
        *current_++ = c;
        return *this;
    }

    // 输出迭代器必需的空操作
    FileBufferOutputIterator& operator*() { return *this; }
    FileBufferOutputIterator& operator++() { return *this; }
    FileBufferOutputIterator operator++(int) { return *this; }

    // 手动刷新缓冲区剩余内容
    void flush() {
        if (current_ != buffer_) {
            fwrite(buffer_, 1, current_ - buffer_, file_);
            current_ = buffer_;
        }
    }

    // 返回当前缓冲区位置,用于fmt::format_to的返回值
    char* current() const { return current_; }

private:
    FILE* file_;
    char* buffer_;
    char* buffer_end_;
    char* current_;
};

2. 封装为文件类的format方法

将迭代器整合到自定义文件类中,封装出易用的format方法,直接支持fmt的格式化语法:

class BufferedFile {
public:
    BufferedFile(const char* path, const char* mode)
        : file_(fopen(path, mode)), buffer_{}, output_iter_(file_, buffer_.data(), buffer_.size()) {}

    ~BufferedFile() {
        if (file_) {
            output_iter_.flush();
            fclose(file_);
        }
    }

    // 格式化写入,兼容所有fmt格式参数
    template <typename... Args>
    void format(fmt::format_string<Args...> fmt_str, Args&&... args) {
        fmt::format_to(output_iter_, fmt_str, std::forward<Args>(args)...);
    }

    // 手动触发缓冲区刷新
    void flush() { output_iter_.flush(); }

private:
    FILE* file_;
    std::array<char, 4096> buffer_; // 可根据需求调整固定缓冲区大小
    FileBufferOutputIterator output_iter_;
};

3. 核心优势

  • 基于fmt公开接口扩展,不修改库源码,兼容性强
  • 缓冲区逻辑内聚,符合单一职责,维护性好
  • 写入时直接判断缓冲区状态,避免冗余分支和内存操作
  • 完全兼容fmt的格式化语法,保持和fmt::format一致的易用性

4. 替代方案:继承fmt::basic_memory_buffer

如果更贴近你之前的验证思路,可以继承fmt的basic_memory_buffer,重写grow方法实现自动刷新,同样无需修改库代码:

template <typename Char>
class FileBackedBuffer : public fmt::basic_memory_buffer<Char, 4096> {
public:
    explicit FileBackedBuffer(FILE* file) : file_(file) {}

protected:
    // 缓冲区满时触发刷新,重置缓冲区而非扩容
    void grow(size_t) override {
        flush();
        this->clear();
    }

public:
    void flush() {
        if (!this->empty()) {
            fwrite(this->data(), sizeof(Char), this->size(), file_);
            this->clear();
        }
    }

    ~FileBackedBuffer() { flush(); }

private:
    FILE* file_;
};

// 对应BufferedFile的format方法实现
template <typename... Args>
void BufferedFile::format(fmt::format_string<Args...> fmt_str, Args&&... args) {
    FileBackedBuffer<char> buffer(file_);
    fmt::format_to(std::back_inserter(buffer), fmt_str, std::forward<Args>(args)...);
}

内容的提问来源于stack exchange,提问作者Lambo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 09:30:58