如何在语句结束时触发操作?

How to trigger action on end-of-statement?

假设我想创建一个流,它会在语句的末尾执行一个动作,这样

myStream << "Hello, " << "World!";

将打印 "Hello, World!\n" 一次 。不是 "Hello, \nWorld!\n" 也不是 "Hello, World!",而是 "Hello, World\n",就好像 ; 会触发追加 \n 并刷新缓冲区。

这样做的基本原理是流 class 写入 stdout 和日志文件,日志文件条目具有特定的前缀和后缀。

例如,如果我的目标是 HTML 我会想要这个代码:

myStream << "Hello, " << "World!";
myStream << "Good bye, " << "cruel World!";

打印成这样:

<p>Hello, World!</p>
<p>Good bye, cruel World!</p>

不是这样的:

<p>Hello, </p><p>World!</p>
<p>Good bye, </p><p>cruel World!</p>

现在,如果我实现 LogStream 有点像这样:

LogStream & LogStream::operator<<( const std::string & text );

我无法区分语句中间的 << 和语句 beginning/ending 中的

如果我实现 LogStream 有点像这样:

LogStream LogStream::operator<<( const std::string & text );

并尝试在析构函数中修改输入我会在块的末尾同时获得多个析构函数。

最后,我可以在每个语句的末尾实现这个我的要求 endl,但我不想打扰调用者这样做的必要性。

因此问题来了:如何以对调用方透明的方式实现这样的流?

行:

myStream << "Hello, " << "World!";

其实就是多条语句。相当于:

ostream& result = myStream << "Hello, ";
result << "World!";

有一些技巧可以通过返回一个未命名的临时对象(在完整表达式的末尾被销毁)来做你想做的事。 Here is some example code.

我曾经为自定义日志系统实现过类似的功能。我创建了一个缓冲输入的 class,然后它的析构函数将缓冲区刷新到我的日志文件。

例如:

#include <iostream>
#include <sstream>
#include <string>

class LogStream
{
private:
    std::stringstream m_ss;

    void flush()
    {
        const std::string &s = m_ss.str();
        if (s.length() > 0)
        {
            std::cout << s << std::endl;
            logfile << "<p>" << s << "</p>" << std::endl;
        }
    }    

public:
    LogStream() {}

    ~LogStream()
    {
        flush();
    }

    template <typename T>
    LogStream& operator<<(const T &t)
    {
        m_ss << t;
        return *this;
    }

    template <typename T>
    LogStream& operator<<( std::ostream& (*fp)(std::ostream&) )
    {
        // TODO: if fp is std::endl, write to log and reset m_ss
        fp(m_ss);
        return *this;
    }

    void WriteToLogAndReset()
    {
        flush();
        m_ss.str(std::string());
        m_ss.clear();
    }
};

对于在最终 ; 上刷新的单个语句,每条消息将使用 class:

的新实例
LogStream() << "Hello, " << "World!";
LogStream() << "Good bye, " << "cruel World!";

要允许多个语句写入单个消息,请创建对象并且在最后一个语句完成之前不要让它超出范围:

{
LogStream myStream;
myStream << "Hello, ";
myStream << "World!";
}

{
LogStream myStream;
myStream << "Good bye, ";
myStream << "cruel World!";
}

要为多条消息重用现有实例,请告诉它在每条消息之间刷新和重置:

{
LogStream myStream;
myStream << "Hello, " << "World!";
myStream.WriteToLogAndReset();
myStream << "Good bye, " << "cruel World!";
}

我喜欢这种方法,因为它使调用者可以更灵活地决定何时准备好将每条消息写入日志文件。例如,我使用它来将多个值发送到单个日志消息,其中这些值是从决策代码分支中获取的。这样,我可以流式传输一些值、做出一些决定、流式传输更多值等,然后将完成的消息发送到日志。

我有一些代码,我还没有抽出时间来做任何有建设性的事情,我认为它正在做你要求的事情。

它通过使用 代理 class (log_buffer) 在 std::stringstream 对象中构建字符串来工作。在表达式的末尾 log_buffer proxy 对象调用主 log_writer 对象处理 std::stringstream.

中包含的整行
class log_writer
{
    // ultimate destination
    std::ostream* sink = nullptr;

    // proxy class to do the << << << chaining
    struct log_buffer
    {
        log_writer* lw;
        std::stringstream ss;

        void swap(log_buffer& lb)
        {
            if(&lb !=this)
            {
                std::swap(lw, lb.lw);
                std::swap(ss, lb.ss);
            }
        }

        log_buffer(log_writer& lw): lw(&lw) {}
        log_buffer(log_buffer&& lb): lw(lb.lw), ss(std::move(lb.ss)) { lb.lw = nullptr; }
        log_buffer(log_buffer const&) = delete;

        log_buffer& operator=(log_buffer&& lb)
        {
            swap(lb);
            return *this;
        }

        log_buffer& operator=(log_buffer const&) = delete;

        // update the log_writer after the last call to << << <<
        ~log_buffer() { if(lw) lw->add_line(ss); }

        template<typename DataType>
        log_buffer operator<<(DataType const& t)
        {
            ss << t;
            return std::move(*this);
        }
    };

    void swap(log_writer& lw)
    {
        if(&lw != this)
        {
            std::swap(sink, lw.sink);
        }
    }

public:
    log_writer(std::ostream& sink): sink(&sink) {}
    log_writer(log_writer&& lw): sink(lw.sink) { lw.sink = nullptr; }
    log_writer(log_writer const&) = delete;

    log_writer& operator=(log_writer&& lw)
    {
        swap(lw);
        return *this;
    }

    log_writer& operator=(log_writer const&) = delete;

    // output the final line
    void add_line(std::stringstream& ss)
    {
        // Do any special line formatting here
        if(sink) (*sink) << ss.str() << std::endl;
    }

    template<typename DataType>
    struct log_buffer operator<<(DataType const& data)
    {
        return std::move(log_buffer(*this) << data);
    }
};

int main()
{
    std::ofstream ofs("test.log");
    log_writer lw1(ofs);
    log_writer lw2(std::cout);

    lw1 << "lw1 " << 2.93 << " A";
    lw2 << "lw2 " << 3.14 << " B";

    std::swap(lw1, lw2);

    lw1 << "lw1 " << 2.93 << " C";
    lw2 << "lw2 " << 3.14 << " D";
}