首页 > 解决方案 > C++ ostream 是否位于行首?

问题描述

在 C++ 中,如何检测 mystd::ostream os是否位于行首,换句话说(我认为)最近写入的os内容是os<<'\n'or os<<std::endl(),或者尚未写入任何内容os

乍一看,这听起来没有必要,因为我可以自己跟踪状态。但是下面是一个常见的情况,其中跟踪将涉及以非常不自然的方式更改os<<thing可能从块中调用的每个语句。try

try {
  do_something_which_writes_to(std::cout);
}
catch(const My_error&error) {
  print_a_newline_if_necessary(std::cout);
  std::cout<<error<<"\n";
}

(在现实中,当然,我们想写到errorstd::cerr但通常会混在一起,std::cout除非其中一个被重定向,所以我们仍然想std::cout在打印到之前终止该行std::cerr。我特意简化了示例以避免这种分心。)

您可能会认为这os.tellp()将是答案,但tellp()似乎仅适用于std::ofstream. 至少对我来说,std::cout.tellp()总是返回-1,表示不支持。

标签: c++positionostream

解决方案


至少在我阅读内容时,您真正想要的不是获得当前行中的位置的能力。相反,您真正想要的是能够打印一些保证在行首的东西——如果前一个字符是换行符,则为当前行(而且,我猜它是否是一个马车return),否则打印一个换行符,然后是后面的任何内容。

这里有一些代码可以做到这一点:

#include <iostream>

class linebuf : public std::streambuf
{
    std::streambuf* sbuf;
    bool            need_newline;

    int sync() {
        return sbuf->pubsync();
    }
    int overflow(int c) {
        switch (c) { 
            case '\r':
            case '\n': need_newline = false;
                break;
            case '\v':
                if (need_newline) { 
                    need_newline = false;
                    return sbuf->sputc('\n');
                }
                return c;
            default:
                need_newline = true;
                break;
        }
        return sbuf->sputc(c);
    }
public:
    linebuf(std::streambuf* sbuf)
        : sbuf(sbuf)
        , need_newline(true)
    {}
    std::streambuf *buf() const { return sbuf; }
    ~linebuf() { sync(); }
};

class linestream : public std::ostream {
    linebuf buf;
    std::ostream &os;
public:
    linestream(std::ostream& out)
        : buf(out.rdbuf())
        , std::ios(&buf)
        , std::ostream(&buf)
        , os(out)
    {
        out.rdbuf(&buf);
    }
    ~linestream() { os.rdbuf(buf.buf()); }
};

void do_stuff() { 
    std::cout << "\vMore output\v";
}

int main() {
    { 
       linestream temp(std::cout);

        std::cout << "\noutput\n";
        std::cout << "\voutput";
        do_stuff();
        std::cout << "\voutput\n";
        std::cout << "\voutput\v";
    }
    std::cout << "\voutput\v";
}

由于它几乎从未以其他方式使用,因此我劫持了垂直制表符 ( '\v') 来表示特殊行为。

要使用它,您只需创建一个临时类型的对象linestream(抱歉,我现在想不起一个好名字),传递给它一个 ostream 对象,当 a\v被写入它时,该对象将获得新的行为。当该临时对象超出范围时,流将恢复到其原始行为(我怀疑是否有人\v经常使用来关心,但谁知道也许有人关心——无论如何,这主要只是清理自身的副作用)。

在任何情况下,特殊行为在do_stuff被调用时仍然存在,因此它不仅仅是创建本地对象的函数的本地linestream对象,或者类似的东西——一旦它被创建,特殊行为就会一直有效,直到它被销毁。

不过还有一点:当/如果您混合来自coutand的输出时cerr,这将无济于事。特别是,双方都不会知道对方的状态。您可能非常需要一些挂钩到输出终端(或该订单上的某些东西)才能处理这个问题,因为输出重定向通常由操作系统处理,因此在程序内部甚至无法猜测数据是否写入去coutcerr是否去同一个地方。


推荐阅读