【问题标题】:Flexible logger class using standard streams in C++在 C++ 中使用标准流的灵活记录器类
【发布时间】:2026-01-01 02:05:02
【问题描述】:

我想创建一个灵活的记录器类。我希望它能够将数据输出到文件或标准输出。另外,我想使用流。该类应如下所示:

class Logger
{
private:
   std::ostream m_out; // or ofstream, iostream? i don't know
public:

   void useFile( std::string fname);
   void useStdOut();

   void log( symbol_id si, int val );
   void log( symbol_id si, std::string str );
   //etc..
};

symbol_id 是一个枚举并定义了格式。我想要实现的是能够轻松地从标准输出切换到文件,反之亦然(这是use* 方法的目的)。最好只使用m_out 并简单地编写m_out << "something"; 而不检查我是否要写入文件或标准输出。

我知道有很多方法可以解决这个问题(使用 if's 测试我是否要写入文件或标准输出,“C 方式”(使用 FILE*fprintf)) 等等,但我确信必须有一种方法可以用 C++ 流以一种很好的方式实现这一点。但我似乎无法找到如何做到这一点。有人可以帮帮我吗?

【问题讨论】:

    标签: c++ logging stream


    【解决方案1】:

    我之前解决这个问题的方法是使Logger 成为一个抽象基类,并创建单独的FileLoggerOutStreamLogger 类。然后创建一个实现Logger接口的CompositeLogger对象,它只输出所有记录器:

    CompositeLogger compLogger;
    compLogger.Add(new FileLogger("output.txt"));
    compLogger.Add(new StreamLogger(std::cout));
    ...
    compLogger.log(...);
    

    如果您不需要这种级别的灵活性并希望将所有这些保留在一个类中,您可以将 m_Out 变量设置为指向 std::ostream 的指针并添加一个额外的标志来跟踪您是否需要删除它关于清理:

    private:
      std::ostream*   m_out;
      bool            m_OwnsStream;
    
    Logger() {
       m_Out=&std::cout; // defaults to stdout
       m_OwnsStream=false;
    }
    void useFile(std::string filename) {
      m_Out=new std::ofstream(filename);
      m_OwnsStream=true;
    }
    ~Logger() {
      if (m_OwnStream) 
        delete m_Out; 
    }
    

    显然您需要在useFileuseStdOut 中进行更多检查以防止内存泄漏。

    【讨论】:

      【解决方案2】:

      C++ 中的std::o*stream 类继承自 std::ostream。这意味着您应该根据 std::ofstream 指针或引用来编写接口:

      class Logger
      {
          std::ostream *m_out; // use pointer so you can change it at any point
          bool          m_owner;
      public:
          // constructor is trivial (and ommited)
          virtual ~Logger()
          {
              setStream(0, false);
          }
          void setStream( std::ostream* stream, bool owner )
          {
              if(m_owner)
                  delete m_out;
              m_out = stream;
              m_owner = owner;
          }
          template<typename T>
          Logger& operator << (const T& object)
          {
              if(!m_out)
                  throw std::runtime_error("No stream set for Logger class");
              (*m_out) << object;
              return *this;
          }
      };
      
      // usage:
      Logger logger;
      logger.setStream( &std::cout, false ); // do not delete std::cout when finished
      logger << "This will be logged to std::cout" << std::endl;
      // ...
      logger.setStream( 
          new std::ofstream("myfile.log", std::ios_base::ate|std::ios_base::app), 
          true ); // delete the file stream when Logger goes out of scope
      logger << "This will be appended to myfile.log" << std::endl;
      

      【讨论】:

      • 谢谢。因为它是正确的,所以也支持你的答案,但是 the_mandrill 很快发布了类似的东西,所以他得到了“接受的答案”标志;)
      • @sree,这两个操作都不是线程安全的(即您可以在竞争条件下将两条消息发送到同一个日志,您可以在竞争条件下发送数据并更改日志记录指针,您可以在竞争条件下设置两个不同的日志流)。
      • 越野车! [错误] 不完整类型的无效使用 'std::ofstream {aka class std::basic_ofstream}' 并且不能传递额外的参数。
      • @sree,对于那个错误,#include &lt;fstream&gt;。我不确定“不能传递额外参数”是什么意思(额外参数是什么?你想做什么?)
      【解决方案3】:

      博士。 Dobbs 发表了一篇文章,我以此作为记录日志的灵感。值得一读。 http://www.drdobbs.com/cpp/201804215

      好像最近也发表了另一篇文章,但我还没有阅读。 http://www.drdobbs.com/cpp/225700666

      【讨论】:

        【解决方案4】:

        the_mandrill 解决方案的一种变体,因为我认为策略模式在概念上更适合这个问题。
        我们可以随时通过调用 context->SetLogger 来改变日志策略。
        我们还可以为文件记录器使用不同的文件。

        class Logger
        {
        protected:
            ostream* m_os;
        public:
            void Log(const string& _s)
            {
                (*m_os) << _s;
                m_os->flush();
            }
        };
        
        class FileLogger : public Logger
        {
            string m_filename;
        public:
            explicit FileLogger(const string& _s)
            : m_filename(_s)
            {
                m_os = new ofstream(m_filename.c_str());
            }
            ~FileLogger()
            {
                if (m_os)
                {
                    ofstream* of = static_cast<ofstream*>(m_os);
                    of->close();
                    delete m_os;
                }
            }
        };
        
        class StdOutLogger : public Logger
        {
        public:
            StdOutLogger()
            {
                m_os = &std::cout;    
            }
        };
        
        class Context
        {
            Logger* m_logger;
        public:
            explicit Context(Logger* _l)  {m_logger = _l;}
            void SetLogger(Logger* _l)    {m_logger = _l;}
            void Log(const string& _s)
            {
                if (m_logger)
                {
                    m_logger->Log(_s);
                }
            }
        };
        
        int main()
        {
            string filename("log.txt");
        
            Logger*  fileLogger   = new FileLogger(filename);
            Logger*  stdOutLogger = new StdOutLogger();
            Context* context      = new Context(fileLogger);
        
            context->Log("this log out to file\n");
            context->SetLogger(stdOutLogger);
            context->Log("this log out to standard output\n");
        }
        

        【讨论】: