C++ ostream 是在一行的开头吗?

Is the C++ ostream at the start of a line?

在 C++ 中,如何检测我的 std::ostream os 是否在一行的开头,换句话说(我认为)最近写入 os 的内容是 os<<'\n'os<<std::endl(),否则尚未向 os?

写入任何内容

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

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::cerr 之前终止 std::cout 行。我特意简化了示例以避免这种干扰。)

您可能认为 os.tellp() 会是答案,但 tellp() 似乎只适用于 std::ofstream。至少对我来说,std::cout.tellp()总是returns-1,说明不支持。

至少在我阅读的时候,您真正想要的不是在当前行中获得位置的能力。相反,您真正想要的是能够打印保证位于行首的内容——如果前一个字符是换行符,则为当前行(而且,我猜它是否也是一个回车符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 对象,它将获得新的\v 写入时的行为。当该临时对象超出范围时,流将恢复到其原始行为(我怀疑是否有人经常使用 \v 来关心,但谁知道也许有人关心 - 这主要只是清理的副作用无论如何在它自己之后)。

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

还有一点:when/if 你混合了 coutcerr 的输出,这没有多大帮助。特别是,两者都不会完全了解对方的状态。你可能非常需要一些挂钩到输出终端(或那个订单上的东西)才能处理这个问题,因为输出重定向通常由 OS 处理,所以在程序内部甚至没有办法猜测写入 coutcerr 的数据是否会去同一个地方。