重载运算符<<C++中带前缀

Overloading operator<< in C++ with a prefix

本文关键字:lt 前缀 C++ 重载 运算符      更新时间:2023-10-16

我正在C++中研究具有以下语法的记录器类:

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

它打印:字符串 1 6.2

这是我的班级的样子:

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;
    }
};

它工作正常,但我也想为每一行添加一个前缀(例如时间戳)。所以当我写的时候:

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

我希望显示这样的东西:

11:59:12 a string 1 6.2

我想到了几个解决方案:

1.将每个输入存储在列表/流中,并使用额外的功能打印然后清除列表/流:

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

2.将每个输入存储在列表/流中,并在检测到"换行符"字符后打印所有内容。然后清除内部流/列表。

这两种解决方案都很好,但我宁愿只将它们用作最后的手段。

有没有其他/更好的方法来实现我想要的?

std::stringbuf派生一个类,比如LoggerStringBuf,并在其构造函数中给它一个对输出std::ofstream的引用。 重写虚拟std::stringbuf::sync()方法以从基std::stringbuf::str()方法检索std::string,并在将其写入std::ofstream时为其添加时间戳前缀。 这样,每次LoggerStringBuf对象出于任何原因刷新到std::ofstream时,无论是通过std::endlstd::flush显式刷新,还是由其析构函数隐式刷新时,都会生成新的时间戳。

然后让你的Logger类从std::ostream派生,并使用LoggerStringBuf对象对其进行初始化。 然后,您可以将输入值流式传输到Logger,它们将被缓存在LoggerStringBuf对象中,直到刷新到std::ofstream。 此时,您可以根据需要预置时间戳。

例如:

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;
    }
};

您需要为 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_); }
};

实际代码会更复杂,但请尝试实现以下逻辑。

添加一个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;
}

为了更一般,您应该扫描data以查找嵌入的换行符,并将时间放在每个换行符之后。

上面的伪代码掩盖了棘手的部分。由于data可以是任何类型,因此last_char(data)(以及更一般的输出扫描以查找嵌入式换行符)并非易事。实现它的一般方法可能是将data写入std::stringstream。然后你可以扫描这个字符串的换行符,最后将字符串输出到*m_pOutStream