【问题标题】:Is the C++ ostream at the start of a line?C++ ostream 是否位于行首?
【发布时间】:2021-03-30 06:56:08
【问题描述】:

在 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";
}

(实际上,当然,我们想将error 写入std::cerr,但通常会与std::cout 混在一起,除非其中一个被重定向,所以我们仍然希望在之前终止std::cout 行打印到std::cerr。我特意简化了示例以避免这种干扰。)

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

【问题讨论】:

  • cout 甚至可能不知道写入缓冲区的内容。它可能已写入底层媒体并且早已不复存在。在处理文件时您可以侥幸逃脱,因为在您关闭文件之前该文件(可能)不会去任何地方。串行端口、网络套接字或任何支持命令控制台的东西,Crom 只知道数据去了哪里以及多久之前。

标签: c++ position ostream


【解决方案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 对象,当@ 987654324@ 被写入其中。当该临时对象超出范围时,流将恢复到其原始行为(我怀疑是否有人经常使用\v 来关心,但谁知道也许有人关心——这主要是清理自身的副作用无论如何)。

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

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

【讨论】:

  • 非常感谢你的课程 - 你付出了很多努力,我一直害怕深入研究 std::streambuf 及其朋友。顺便说一句,我认为构造函数可能想要初始化need_newline(false)。但我会等待,以防有办法使用现有的std::ostream API,然后再将其标记为已接受的答案。
  • @AdamChalcraft:最初应该是真还是假取决于你想要什么(可能我没有仔细阅读但我在开头没有看到你想要什么的明确定义输出。但无论如何,你看明白如何让它做你想做的事......
  • 至于coutcerr 的事情,我同意没有完美的答案,但我对{std::cout&lt;&lt;"\v"; std::cerr&lt;&lt;"\verror\n";} 的解决方案很满意。在最坏的情况下,它会在无论如何都发生错误的情况下将'\n' 和额外的std::cout 写入。
猜你喜欢
  • 2015-03-08
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 2018-03-31
  • 2014-09-11
  • 2021-08-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多