如何正确实现基于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
相关产品推荐
相关产品推荐

