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

如何实现支持传递含<<重载对象的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 04:30:50