How to create functions like std::cout?

后端 未结 6 632
盖世英雄少女心
盖世英雄少女心 2021-01-03 16:40

I\'m creating my own logging utility for my project, I want to create a function like iostream\'s std::cout, to log to a file and print to the console as well.

Here\

6条回答
  •  失恋的感觉
    2021-01-03 16:53

    There are two problems I see above. The first is forking your message (to both a file, and the console). The second is wrapping what is written with some extra stuff.

    meta_stream handles the operator<< overloading. It uses CRTP to statically dispatch to its child type:

    template
    struct meta_stream {
      D& self() { return *static_cast(this); } // cast myself to D
      // forwarders of operator<<
      template
      friend D& operator<<( meta_stream& d, X const& x ) {
        d.self().write_to(x);
        return d.self();
      }
      friend D& operator<<(
        meta_stream& d,
        substream&(*mod_func)(substream&)
      ) {
        d.self().write_to(mod_func);
        return d.self();
      }
    };
    

    I had to override << twice because of how std::endl and other modifiers work -- they are the name of an overloaded function.

    This solves the problem of outputing the same string to two different ostreams:

    template
    struct double_ostream:
      meta_stream,substream>
    {
      substream* a = nullptr;
      substream* b = nullptr;
      template
      void write_to( X&&x ) {
        if (d.a) (*d.a) << x;
        if (d.b) (*d.b) << std::forward(x);
      }
      double_ostream( std::basic_ostream* a_, std::basic_ostream* b_ ):
        a(a_), b(b_)
      {}
      double_ostream(double_ostream const&)=default;
      double_ostream()=default;
      double_ostream& operator=(double_ostream const&)=default;
    };
    

    note the use of CRTP via meta_stream. I just have to implement write_to.

    First, write your 4 loggers to this array:

    enum loglevel {
      debug, error, warning, info
    };
    double_stream loggers[4];
    

    giving each a pointer to a std::cout and a pointer to a (stored elsewhere) stream wrapping a file you want to save the log to. You can pass nullptr if you don't want that level to be logged to that output stream (say, in release, skip debug logs), and you can log stuff to different log file (debug to one file, info to another).

    double_stream log( loglevel l ) {
      double_stream retval = loggers[l];
      std::string message;
      // insert code to generate the current date here in message
      // insert code to print out the log level here into message
      retval << message;
      return retval;
    }
    

    now log(debug) << "hello " << "world\n" will write your message for you.

    You can do more fancy stuff if you don't want to write the newline at the end of the log message, but I doubt it is worth it. Just write the newline.

    If you really want that feature:

    template
    struct write_after_ostream:
      meta_stream,substream>
    {
      substream* os = nullptr;
      template
      void write_to( X&&x ) {
        if (os) *os << std::forward(x);
      }
      ~write_after_ostream() {
        write_to(message);
      }
      write_after_ostream( substream* s, std::string m ):
        os(s), message(m)
      {}
      std::string message;
    }
    
    write_after_ostream> log( loglevel l ) {
      // note & -- store a reference to it, as we will be using a pointer later:
      double_stream& retval = loggers[l];
      std::string pre_message;
      // insert code to generate the current date here in pre_message
      // insert code to print out the log level here into pre_message
      retval << pre_message;
      return {&retval, "\n"};
    }
    

    but I don't think it is worth it.

提交回复
热议问题