如何实现支持传递含<<重载对象的C++日志系统?
流风格日志系统的类型适配方案(非动态分派)
问题描述
尝试实现一个通过流运算符输出的日志系统,期望用法:
// 当前每个logger仅支持一个transport,后续可扩展CompositeTransport实现多transport组合 logger.setTransport(ConsoleTransport); logger.log(Severity::Warning) << "This is a warning";
已实现的核心接口如下,但遇到CustomCoutStream无法转换为std::ostream的问题,希望不使用动态分派,通过运算符重载或类似trait的机制,兼容任意重载了<<的流对象:
using LogTimestamp = std::chrono::time_point<std::chrono::system_clock>; using LogTransport = std::function<std::ostream (Severity, LogTimestamp)>; void Logger::addTransport(LogTransport transport) { mTransport = transport; } std::ostream Logger::log(Severity severity) { auto timestamp = std::chrono::system_clock::now(); return mTransport(severity, timestamp); } std::ostream ConsoleTransport(LogSeverity severity, LogTimestamp timestamp) { // 自定义流,析构时自动添加换行并刷新 CustomCoutStream stream(std::cout); // 报错:CustomCoutStream无法转换为std::ostream return stream; }
解决方案:基于模板代理类的类型擦除
核心思路是用代理类替代直接返回std::ostream,代理类通过模板实现类型擦除,仅析构时用一次虚调用,可以持有任意支持<<运算符的流对象,并转发所有输出操作。
1. 实现流代理类
这个类负责持有自定义流,转发<<操作,并保留自定义流的生命周期行为:
#include <utility> #include <ostream> #include <memory> class LogStreamProxy { public: // 接收任意可移动的流对象 template<typename Stream> explicit LogStreamProxy(Stream&& stream) : m_impl(std::make_unique<Impl<Stream>>(std::forward<Stream>(stream))) {} // 仅允许移动,禁止拷贝(避免流对象被重复持有) LogStreamProxy(LogStreamProxy&&) noexcept = default; LogStreamProxy& operator=(LogStreamProxy&&) noexcept = default; LogStreamProxy(const LogStreamProxy&) = delete; LogStreamProxy& operator=(const LogStreamProxy&) = delete; // 重载<<,转发给底层流 template<typename T> LogStreamProxy& operator<<(const T& value) { m_impl->write(value); return *this; } // 支持std::endl这类流操纵器 LogStreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { m_impl->write(manip); return *this; } private: // 抽象基类,用于类型擦除 struct ImplBase { virtual ~ImplBase() = default; virtual void write(std::ostream& (*)(std::ostream&)) = 0; template<typename T> virtual void write(const T&) = 0; }; // 具体实现,持有实际的流对象 template<typename Stream> struct Impl : ImplBase { explicit Impl(Stream&& s) : stream(std::forward<Stream>(s)) {} void write(std::ostream& (*manip)(std::ostream&)) override { manip(stream); } template<typename T> void write(const T& value) override { stream << value; } Stream stream; }; std::unique_ptr<ImplBase> m_impl; };
2. 调整Logger与Transport类型定义
将返回值和Transport的返回类型改为LogStreamProxy:
#include <chrono> #include <functional> enum class Severity { Debug, Info, Warning, Error }; using LogTimestamp = std::chrono::time_point<std::chrono::system_clock>; // 修改Transport的返回类型为LogStreamProxy using LogTransport = std::function<LogStreamProxy(Severity, LogTimestamp)>; class Logger { public: void addTransport(LogTransport transport) { mTransport = std::move(transport); } LogStreamProxy log(Severity severity) { auto timestamp = std::chrono::system_clock::now(); return mTransport(severity, timestamp); } private: LogTransport mTransport; };
3. 实现自定义流与Transport
现在ConsoleTransport可以直接返回自定义流,无需转换:
#include <iostream> // 自定义流,析构时自动换行并刷新 class CustomCoutStream { public: explicit CustomCoutStream(std::ostream& os) : m_os(os) {} ~CustomCoutStream() { m_os << '\n' << std::flush; } // 重载<<支持任意类型 template<typename T> CustomCoutStream& operator<<(const T& value) { m_os << value; return *this; } // 支持流操纵器 CustomCoutStream& operator<<(std::ostream& (*manip)(std::ostream&)) { manip(m_os); return *this; } private: std::ostream& m_os; }; // ConsoleTransport直接返回自定义流的代理 LogStreamProxy ConsoleTransport(Severity severity, LogTimestamp timestamp) { CustomCoutStream stream(std::cout); // 示例:先输出日志级别 stream << "[" << static_cast<int>(severity) << "] "; return LogStreamProxy(std::move(stream)); }
4. 测试代码
int main() { Logger logger; logger.addTransport(ConsoleTransport); logger.log(Severity::Warning) << "This is a warning" << " | additional data: " << 42; return 0; }
核心优势
- 无动态分派开销:除了代理类析构时的一次虚调用,所有输出操作都是直接转发,没有虚函数调用开销。
- 兼容任意自定义流:只要流类重载了
<<运算符,就能被代理类适配,符合"trait式"的类型适配需求。 - 保留自定义流行为:比如
CustomCoutStream的析构自动刷新、换行逻辑完全生效。
内容的提问来源于stack exchange,提问作者Gasim
相关产品推荐
相关产品推荐

