Make a wrapper for cout?

2019-05-24 10:09发布

问题:

So here's an interesting question, How would I make something kinda like a wrapper for cout? I want to be able to add it into a dll so I can throw it into my programs. but the basic syntax of it should be

    Mything::mesage << "I'm some text" << im_an_int << someclass << mything::endl;

or

    Mything::mesageandlog << "I'm going to print to console, and to a file!" << mything::endl;

I can handle most of the internal logic but as to what I should put to even do this. kinda stumped.

Possibly make a static stream member in my class called message, then have an event fire when its written too that runs it through a method?

Idk, I looked around and found something sortA similar, but as for throwing it into a dll I'm at a loss. (How to write a function wrapper for cout that allows for expressive syntax?) because this requires me to use extern and a variable, but how would I make it static so I can just straight call it without creating a variable?

Bit of clarification, something like this: mydll.h

#include <iostream>
namespace mynamespace {

    extern struct LogMessage{};

    template <typename T>
    LogMessage& operator<< (LogMessage &s, const T &x) {
        SetStdHandle(STD_OUTPUT_HANDLE, GetStdHandle(STD_OUTPUT_HANDLE));
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_BLUE);
        std::cout << "[IF] ";
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_WHITE);
        //LogTimestamp(); --ill impliment this.
        std::cout << x << endl;
        //writeStreamToLogfile(s); --and ill handle this.
        return s;
    }
}

driverprogram.h

#include <mydll.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
    mynamespace::LogMessage << "Something: << std::endl;
}

expected output:

"[IF] [00:00:00] Something

回答1:

You can create a struct, that has a << operator

struct OutputThing
{

  template< class T >
  OutputThing &operator<<( T val )
  {
    std::cout<<val;
    return *this;
  }
};

Now whenever you want to log, you will have to instance the object.

OutputThing()<<"x ="<<x;

If you want to avoid the repeated construction and destruction of the object, you can make it a singleton.

struct OutputThingSingleton
{
  static OutputThingSingleton& GetThing()
  {
    static OutputThingSingleton OutputThing;
    return OutputThing;
  }

  template< class T >
  OutputThingSingleton &operator<<( T val )
  {
    std::cout<<val;
    return *this;
  }

private:
  OutputThingSingleton()
  {};

};

So the call now looks like

OutputThingSingleton::GetThing()<<"x ="<<x;

Which you could shorten using a macro.

This will work across multiple dlls, however depending on how it is used you can have multiple instances of the singleton existing. This would work fine as long as you don't want to maintain any state in your singleton. If you do need to ensure a single instance, you can compile it in its own dll. Any other binary that uses this dll will share the single instance 'owned' by the dll.



回答2:

First of all, just to give fair warning, I'm pretty sure this won't work in a DLL. You want to put it into a header (as it's shown here).

Second, it's probably a little more elaborate than you were considering. In particular, it defines a multi-output stream class that works like any other stream. Essentially any normal overload of operator<< should work fine with it.

Unlike a normal stream operator, however, the output goes to multiple streams, and each line of output (on all the streams) is preceded by a prefix (currently set to the value "[FIX]", but it just uses the content of a string, so whatever you put in that string should work. A more polished/finished implementation would probably allow you to set the prefix with something like a manipulator, but this (currently) doesn't support that.

Finally, it does some variadic template trickery, so you can specify the output files as either file names or existing ostream objects, or a combination thereof (e.g., see demo main at end).

First, the header:

#ifndef LOGGER_H_INC_
#define LOGGER_H_INC_

#include <iostream>
#include <streambuf>
#include <vector>
#include <fstream>


class logger: public std::streambuf {
public:
    logger(std::streambuf* s): sbuf(s) {}
    ~logger() { overflow('\n'); }
private:
    typedef std::basic_string<char_type> string;

    int_type overflow(int_type c) {

        if (traits_type::eq_int_type(traits_type::eof(), c))
            return traits_type::not_eof(c);
        switch (c) {
        case '\n':
        case '\r':  {
            prefix = "[FIX]";
            buffer += c;
            if (buffer.size() > 1)
                sbuf->sputn(prefix.c_str(), prefix.size());
            int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
            buffer.clear();
            return rc;
        }
        default:
            buffer += c;
            return c;
        }
    }

    std::string prefix;
    std::streambuf* sbuf;
    string buffer;
};

namespace multi {
    class buf : public std::streambuf {
        std::vector<std::streambuf *> buffers;
    public:
        typedef std::char_traits<char> traits_type;
        typedef traits_type::int_type  int_type;

        buf() {}

        void attach(std::streambuf *s) { buffers.push_back(s); }
        void attach(std::ofstream &s) { buffers.push_back(s.rdbuf()); }

        int_type overflow(int_type c) {
            bool eof = false;
            for (std::streambuf *buf : buffers)
                eof |= (buf->sputc(c) == traits_type::eof());
            return eof ? traits_type::eof() : c;
        }
    };

    class logstream : public std::ostream {
        std::vector<std::ofstream *> streams;
        buf outputs;
        logger log; 

        void attach(std::ostream &s) { outputs.attach(s.rdbuf()); }
        void attach(char const *name) {
            std::ofstream *s = new std::ofstream(name);
            streams.push_back(s);
            outputs.attach(s->rdbuf());
        }

        template <typename T, typename...pack>
        void attach(T &t, pack&...p) {
            attach(t);
            attach(p...);
        }

    public: 
        template <typename...pack>
        logstream(pack&...p) : log(&outputs), std::ostream(&log) { attach(p...); }

        ~logstream() {
            for (auto d : streams) {
                d->close();
                // Bug: crashes with g++ if delete is allowed to execute.
                //delete d;
            }
        }
    };
}

#endif

Then the demo of how to use it:

#include "logger"

int main(){
    multi::logstream l(std::cout, "c:/path/log.txt");
    l << "This is a prefixed string\n";
}

Obviously the header is fairly large, but the code to use it seems (at least to me) about as simple as you can hope for -- create an object, specifying where you want the output to go, just a normal stream -- except that you can specify more than one. Then write to it like you would to any other stream, and the output goes to all of the specified outputs, with each line preceded by the specified prefix.