【问题标题】:C++ cout with prefix带前缀的 C++ cout
【发布时间】:2015-02-04 19:55:16
【问题描述】:

我想要一个在 cout 上重定向的每行开头都有一个前缀的 ostream; 我试试这个:

#include <iostream>
#include <thread>
class parallel_cout : public std::ostream
{
public:
parallel_cout(std::ostream& o):out(o){}

template <typename T>
std::ostream& operator<< (const T& val)
{
    out << "prefix " << val;
    return *this;
}

std::ostream& out;

};

int main()
{
 parallel_cout pc(std::cout);
 pc<<"a\nb"<<"c\n";
}

但我有输出

prefix a
b

没有c。为什么会这样?

【问题讨论】:

  • 一般来说,从 std::stream 派生不是一个好主意 - 它没有虚拟析构函数,您的代码显示输出操作如何在派生类上不起作用,因为您正在返回对基类的引用。我会尝试扩展为一个完整的答案,除非有人打败我:)
  • 你的模板应该返回parallel_cout&amp;,而不是通用的ostream&amp;
  • @martin_pr std::ostream 有一个虚拟构造函数(继承自 std::ios_base)。此外,std::fstreamstd::stringstream 也是派生类,但它们工作正常。您想要从 IOStreams 基类派生的主要原因是包装自定义 std::streambuf,这是具体流类所做的。
  • @499602D2 很公平,我的错。
  • @volperossa:你真的应该使用模板的方法!像std::endl 这样的操纵器的使用也不会是您问题的终结:当您将流传递给采用std::ostream&amp; 的函数时,它也不会做正确的事情。使用自定义流缓冲区对操纵器以及在将流流传递给采用std::ostream&amp; 的函数时做正确的事情。也就是说:操纵器的问题在于它们是函数模板,std::ostream 有一个合适的输出运算符,用于推断模板参数。

标签: c++ iostream cout ostream


【解决方案1】:

您修改std::ostream 行为的方式是不是通过重载任何输出运算符!相反,您从std::streambuf 派生一个类并覆盖virtual 函数overflow()sync()。在您的情况下,您可能会创建一个过滤流缓冲区,即,您将另一个 std::streambuf 作为参数并将经过某种方式修改的字符流写入此流缓冲区。

这是一个简单的例子:

#include <iostream>

class prefixbuf
    : public std::streambuf
{
    std::string     prefix;
    std::streambuf* sbuf;
    bool            need_prefix;

    int sync() {
        return this->sbuf->pubsync();
    }
    int overflow(int c) {
        if (c != std::char_traits<char>::eof()) {
            if (this->need_prefix
                && !this->prefix.empty()
                && this->prefix.size() != this->sbuf->sputn(&this->prefix[0], this->prefix.size())) {
                return std::char_traits<char>::eof();
            }
            this->need_prefix = c == '\n';
        }
        return this->sbuf->sputc(c);
    }
public:
    prefixbuf(std::string const& prefix, std::streambuf* sbuf)
        : prefix(prefix)
        , sbuf(sbuf)
        , need_prefix(true) {
    }
};

class oprefixstream
    : private virtual prefixbuf
    , public std::ostream
{
public:
    oprefixstream(std::string const& prefix, std::ostream& out)
        : prefixbuf(prefix, out.rdbuf())
        , std::ios(static_cast<std::streambuf*>(this))
        , std::ostream(static_cast<std::streambuf*>(this)) {
    }
};

int main()
{
    oprefixstream out("prefix: ", std::cout);
    out << "hello\n"
        << "world\n";
}

流缓冲区在概念上保留一个内部缓冲区,但是,在上面的示例中没有设置。每当一个字符没有空间写入输出缓冲区时,virtual 函数overflow() 就会被一个字符调用(它也可以用特殊值std::char_traits&lt;char&gt;::eof() 调用,它通常用于刷新缓冲)。由于没有缓冲区,overflow() 将为每个字符调用。这个函数所做的就是看它是否需要写一个前缀,如果需要,写prefix。如果写入换行符'\n',函数会记住如果写入另一个字符,它需要写入prefix。然后它只是将字符的写入转发到底层流缓冲区。

virtual 函数sync() 用于将流与其外部表示同步。对于保留缓冲区的流缓冲区,它确保写入任何缓冲区。由于prefixbuf 并没有真正保留缓冲区,它所要做的就是通过调用pubsync()sync() 请求委托给底层流缓冲区。

【讨论】:

  • 我不知道这些方法(同步和溢出)你知道一个好的教程吗?
  • Nicolai Josuttis 的“C++ 标准库”中解释了 IOStreams 库,显然,这个解释是优秀的(不过,我可能有偏见,特别写了有关创建流缓冲区的部分的原始版本)。还有 Angelika Langer 和 Klaus Kreft 的“IOStreams and Locales”,但这是一整本专门讨论 I/O 的书。否则,如果您在 Google 上查找“streambuf”以及“James Kanze”或“Dietmar Kuehl”,您应该会找到大量资源。
  • 亲爱的 Dietmar,以上示例中的可见性等级 (public/protected/private) 是什么?在 Nicolai 的书中,自定义流缓冲区示例将 protected 分配给 overflow 方法并忽略同步。
  • @shuhalo:prefixbuf 不打算继承自。因此,prefixbufprotected 还是 private 并不重要。当我编写上面的代码时,我可能懒得在overflow() 之前输入protected:如果prefixbuf 是从任何原因继承而来的,那么使派生类可以访问prefixbuf::overflow() 可能很有用。如果我在 Nico 的书中忘记了 sync() 用于输出的过滤流缓冲区,我犯了一个错误,并且 NIco 在将其集成到他的书中时没有更正它:这是哪个示例?
  • 在进入这个线程(2008 年第 21 次印刷)之前,我已经阅读了“C++ 标准库”中的第 13.13.3 章。希望这可以帮助。顺便感谢您的快速回复。
【解决方案2】:

正如 Dietmar 所说,你想要的是你自己的 streambuf,按照这个一般顺序:

#include <streambuf>
#include <iostream>

class prefixer: public std::streambuf {
public:
    prefixer(std::streambuf* s): sbuf(s) {}
    ~prefixer() { 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;
};

要使用它,您可以从其他一些 streambuf(定义它要写入的位置)创建它的一个实例,然后(通常)使用这个 streambuf 创建一个 ostream。然后您可以写入该 ostream,并且您的前缀将写入每一行输出。例如:

int main() { 
    prefixer buf(std::cout.rdbuf());

    std::ostream out(&buf);

    out << "Test\n";
}

【讨论】:

  • 不错的努力,虽然我更喜欢我的版本:对于一个我认为它更简单,对于另一个它不会在最后一个换行符之后创建尾随前缀。它还在第一行添加前缀而没有虚假的换行符。最后,它通过刷新传递到底层流缓冲区。我意识到您的版本是缓冲的,但没有在sync() 上刷新,这会造成一些伤害。为了简单起见,我的版本没有缓冲,尽管我意识到不缓冲流缓冲区会带来潜在的性能问题。
【解决方案3】:

您也可以编写一个函数,尽管语法发生变化。

template<typename T>
void print(T content)
{
    cout<<whatever your prefix is;
    cout<<content;
}

【讨论】:

    【解决方案4】:

    您的输出运算符正在返回对基类 (std::ostream) 的引用,这意味着对 operator &lt;&lt; 的下一次调用将不会使用您的实现。

    将您的输出运算符更改为此应该可以解决您的问题:

    template <typename T>
    parallel_cout& operator<< (const T& val)
    {
        out << "prefix " << val;
        return *this;
    }
    

    【讨论】:

      猜你喜欢
      • 2018-02-15
      • 1970-01-01
      • 1970-01-01
      • 2017-05-04
      • 2018-12-21
      • 1970-01-01
      • 1970-01-01
      • 2015-06-15
      • 1970-01-01
      相关资源
      最近更新 更多