简体   繁体   English

使用前缀在C ++中重载operator <<

[英]Overloading operator<< in C++ with a prefix

I'm working on a logger class in C++ that has the following syntax: 我正在使用C ++编写一个具有以下语法的logger类:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";

And it prints: a string 1 6.2 它打印:字符串1 6.2

This is what my class looks like: 这就是我班上的样子:

class Logger 
{
private:
    unique_ptr<ofstream> m_pOutStream;

public: 
    Logger(std::string sFile) : m_pOutStream(new ofstream(sFile, std::ios::app))
    {}

    template<typename T>
    Logger& operator<< (const T& data)
    {
        *m_pOutStream << data;
        return *this;
    }
};

It works fine, but I would also like to add a prefix to every line (eg a timestamp). 它工作正常,但我还想为每一行添加一个前缀(例如时间戳)。 So when I write: 所以当我写:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";

I want something like this to be displayed: 我想要显示这样的东西:

11:59:12 a string 1 6.2

I have thought of a couple of solutions: 我想到了几个解决方案:

1.Keep every input stored in a list/stream and use an extra function to print and then clear the list/stream: 1.保存列表/流中存储的每个输入,并使用额外的功能进行打印,然后清除列表/流:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";
log.logd(); // <- this prints and then clears the internal stream/list.

2.Keep every input stored in a list/stream and print everything after a "new line" character is detected. 2.保存列表/流中存储的每个输入,并在检测到“新行”字符后打印所有内容。 And clear the internal stream/list after that. 然后清除内部流/列表。

Both of these solutions are nice but I'd prefer to use them only as a last resort. 这两种解决方案都很不错,但我更喜欢将它们作为最后的手段使用。

Is there any other/better way to achieve what I want? 有没有其他/更好的方法来实现我想要的?

You need to introduce an additional wrapper class for Logger that knows whether the line is starting or being appended. 您需要为Logger引入一个额外的包装类,它知道该行是在启动还是被追加。

class Appender
{
    Appender(Logger& logger) : os_(os) { }
    Appender& operator <<(const T& x) { os_ << x; return *this; }
};

class Logger
{
    Appender operator <<(const T& x) { os_ << timestamp() << x; return Appender(os_); }
};

The actual code will be more complicated, but try implementing the following logic. 实际代码会更复杂,但请尝试实现以下逻辑。

Add a member_variable bool last_char_was_newline , and use it in the code like this: 添加一个member_variable bool last_char_was_newline ,并在代码中使用它,如下所示:

template<typename T>
Logger& operator<< (const T& data)
{
    if (last_char_was_newline) {
        *m_pOutStream << current_time_string();
        last_char_was_newline = false;
    }
    *m_pOutStream << data;
    if (last_char(data) == '\n') {
        last_char_was_newline = true;
    }
    return *this;
}

To be more general, you should scan data for embedded newlines, and put the time after each of them as well. 更一般地说,您应该扫描嵌入换行的data ,并将时间也放在每个换行之后。

The above pseudo-code is glossing over the tricky part. 上面的伪代码掩盖了棘手的部分。 Since data can be any type, last_char(data) (and the more general scanning of the output for embedded newlines) is non-trivial. 由于data可以是任何类型,因此last_char(data) (以及对嵌入式换行的输出的更一般扫描last_char(data)last_char(data) A general way to implement it might be to write data to a std::stringstream . 实现它的一般方法可能是将data写入std::stringstream Then you can scan this string for newlines, and finally output the string to *m_pOutStream . 然后,您可以扫描此字符串以获取换行符,最后将该字符串输出到*m_pOutStream

Derive a class from std::stringbuf , say LoggerStringBuf , and give it a reference to your output std::ofstream in its constructor. std::stringbuf LoggerStringBuf派生一个类,比如说LoggerStringBuf ,并在其构造函数中为它输出std::ofstream Override the virtual std::stringbuf::sync() method to retrieve a std::string from the base std::stringbuf::str() method and prefix it with a timestamp when writing it to the std::ofstream . 覆盖虚拟std::stringbuf::sync()方法以从基本std::stringbuf::str()方法中检索std::string ,并在将其写入std::ofstream时为其添加时间戳。 This way you generate a new timestamp every time your LoggerStringBuf object is flushed to the std::ofstream for any reason, whether explicitly by std::endl or std::flush , or implicitly by its destructor. 这样,每次LoggerStringBuf对象因任何原因刷新到std::ofstream时都会生成一个新的时间戳,无论是由std::endl还是std::flush显式,还是由析构函数隐式地生成。

Then have your Logger class derive from std::ostream and initialize it with a LoggerStringBuf object. 然后让您的Logger类派生自std::ostream并使用LoggerStringBuf对象初始化它。 Then you can stream input values to your Logger and they will be cached in your LoggerStringBuf object until flushed to the std::ofstream . 然后,您可以将输入值传输到Logger ,它们将缓存在LoggerStringBuf对象中,直到刷新到std::ofstream At which time you can prepend timestamps as needed. 此时您可以根据需要预先设置时间戳。

For example: 例如:

class LoggerStringBuf : public std::stringbuf
{
private:
    std::ostream &m_OutStream;

protected:
    virtual int sync()
    {
        int ret = std::stringbuf::sync();

        std::string s = str();
        str("");

        // note sure if the string includes non-flushing
        // line breaks.  If needed, you can use std::getline()
        // to break up the string into multiple lines and 
        // write a timestamp for each line...
        //
        m_OutStream << "[timestamp] " << s << std::endl;

        return ret;
    };

public:
    LoggerStringBuf(std::ostream &OutStream)
        : std::stringbuf(std::ios_base::out), m_OutStream(OutStream)
    {
    }

    ~LoggerStringBuf()
    {
        sync();
    }
};

class Logger : public std::ostream
{
private:
    std::ofstream m_OutStream;
    LoggerStringBuf m_Buf;

public: 
    Logger(const std::string &sFile)
        : std::ostream(0), m_OutStream(sFile, std::ios::app), m_Buf(m_OutStream)
    {
        init(&m_Buf);
    }

    template<typename T>
    std::ostream& operator<< (const T& data)
    {
        return static_cast<std::ostream&>(*this) << data;
    }
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM