使用std :: ostream在每行之前插入文本

时间:2014-05-15 20:55:46

标签: c++ stl iostream

我想知道是否可以从std :: ostream继承,并以某种方式覆盖flush(),以便将某些信息(例如行号)添加到每行的开头。然后我想通过rdbuf()将它附加到std :: ofstream(或cout),以便我得到这样的东西:

ofstream fout("file.txt");
myostream os;
os.rdbuf(fout.rdbuf());

os << "this is the first line.\n";
os << "this is the second line.\n";

会把它放到file.txt

1 this is the first line.
2 this is the second line.

2 个答案:

答案 0 :(得分:5)

flush()不会成为在此背景下覆盖的功能,尽管您处于正确的轨道上。您应该在基础overflow()界面上重新定义std::streambuf。例如:

class linebuf : public std::streambuf
{
public:
    linebuf() : m_sbuf() { m_sbuf.open("file.txt", std::ios_base::out); }

    int_type overflow(int_type c) override
    {
        char_type ch = traits_type::to_char_type(c);
        if (c != traits_type::eof() && new_line)
        {
            std::ostream os(&m_sbuf);
            os << line_number++ << " ";
        }

        new_line = (ch == '\n');
        return m_sbuf.sputc(ch);
    }

    int sync() override { return m_sbuf.pubsync() ? 0 : -1; }
private:
    std::filebuf m_sbuf;
    bool new_line = true;
    int line_number = 1;
};

现在你可以做到:

linebuf buf;
std::ostream os(&buf);

os << "this is the first line.\n";  // "1 this is the first line."
os << "this is the second line.\n"; // "2 this is the second line."

Live example

答案 1 :(得分:2)

詹姆斯·坎泽的classic article on Filtering Streambufs有一个非常相似的例子,它将时间戳放在每一行的开头。你可以调整那段代码。

或者,您可以使用由该文章中的想法构成的Boost工具。

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/array.hpp>
#include <cstring>
#include <limits>

// line_num_filter is a model of the Boost concept OutputFilter which
// inserts a sequential line number at the beginning of every line.
class line_num_filter
    : public boost::iostreams::output_filter
{
public:
    line_num_filter();

    template<typename Sink>
    bool put(Sink& snk, char c);

    template<typename Device>
    void close(Device&);

private:
    bool m_start_of_line;
    unsigned int m_line_num;
    boost::array<char, std::numeric_limits<unsigned int>::digits10 + 4> m_buf;
    const char* m_buf_pos;
    const char* m_buf_end;
};

line_num_filter::line_num_filter() :
    m_start_of_line(true),
    m_line_num(1),
    m_buf_pos(m_buf.data()),
    m_buf_end(m_buf_pos)
{}

// put() must return true if c was written to dest, or false if not.
// After returning false, put() with the same c might be tried again later.
template<typename Sink>
bool line_num_filter::put(Sink& dest, char c)
{
    // If at the start of a line, print the line number into a buffer.
    if (m_start_of_line) {
        m_buf_pos = m_buf.data();
        m_buf_end = m_buf_pos +
            std::snprintf(m_buf.data(), m_buf.size(), "%u ", m_line_num);
        m_start_of_line = false;
    }

    // If there are buffer characters to be written, write them.
    // This can be interrupted and resumed if the sink is not accepting
    // input, which is why the buffer and pointers need to be members.
    while (m_buf_pos != m_buf_end) {
        if (!boost::iostreams::put(dest, *m_buf_pos))
            return false;
        ++m_buf_pos;
    }

    // Copy the actual character of data.
    if (!boost::iostreams::put(dest, c))
        return false;

    // If the character copied was a newline, get ready for the next line.
    if (c == '\n') {
        ++m_line_num;
        m_start_of_line = true;
    }
    return true;
}

// Reset the filter object.
template<typename Device>
void line_num_filter::close(Device&)
{
    m_start_of_line = true;
    m_line_num = 1;
    m_buf_pos = m_buf_end = m_buf.data();
}


int main() {
    using namespace boost::iostreams;
    filtering_ostream myout;
    myout.push(line_num_filter());
    myout.push(std::cout);

    myout << "this is the first line.\n";
    myout << "this is the second line.\n";
}