【问题标题】:Restore the state of std::cout after manipulating it操作后恢复 std::cout 的状态
【发布时间】:2011-01-17 09:59:51
【问题描述】:

假设我有这样的代码:

void printHex(std::ostream& x){
    x<<std::hex<<123;
}
..
int main(){
    std::cout<<100; // prints 100 base 10
    printHex(std::cout); //prints 123 in hex
    std::cout<<73; //problem! prints 73 in hex..
}

我的问题是,从函数返回后,是否有任何方法可以将cout 的状态“恢复”到其原始状态? (有点像std::boolalpha 和std::noboolalpha..)?

谢谢。

【问题讨论】:

  • 我相信十六进制只持续到下一次移出操作。仅当您手动更改格式标志而不是使用操纵器时,更改才会持久。
  • @BillyONeal:不,使用操纵器与手动更改格式标志的效果相同。 :-P
  • 如果您是因为 Covertiy 发现 未恢复 ostream 格式 (STREAM_FORMAT_STATE) 而来到这里的,请参阅 Coverity finding: Not restoring ostream format (STREAM_FORMAT_STATE)。
  • 我做了类似的事情 - 请参阅我的代码审查问题:Use a standard stream, and restore its settings afterwards。
  • 这个问题很好地说明了为什么 iostream 不比 stdio 好。刚刚发现两个讨厌的错误,因为 not-/semi-/fully-/what-not 持久性 iomanip。

标签: c++ iostream


【解决方案1】:

您需要#include &lt;iostream&gt; 或#include &lt;ios&gt; 然后在需要时:

std::ios_base::fmtflags f( cout.flags() );

//Your code here...

cout.flags( f );

您可以将它们放在函数的开头和结尾,或者查看this answer 了解如何将其与RAII 一起使用。

【讨论】:

  • @ChrisJester-Young,实际上好的 C++ 是 RAII,尤其是在这种情况下!
  • @Alexis 我 100% 同意。请参阅我的答案(Boost IO Stream State Saver)。 :-)
  • 这不是异常安全的。
  • 除了标志之外,流状态还有更多内容。
  • 您可以通过不将格式推送到流上来避免该问题。将格式和数据推送到一个临时的字符串流变量中,然后打印
【解决方案2】:

Boost IO Stream State Saver 似乎正是您所需要的。 :-)

基于您的代码 sn-p 的示例:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}

【讨论】:

  • 请注意,这里没有魔法,ios_flags_saver 基本上只是像@StefanKendall 的回答一样保存和设置标志。
  • @einpoklum 但与其他答案不同,它是异常安全的。 ;-)
  • 除了标志之外,流状态还有更多内容。
  • @jww IO Stream State Saver 库有多个类,用于保存流状态的不同部分,ios_flags_saver 只是其中之一。
  • 如果您认为值得自己重新实现和维护每一件小事,而不是使用经过审查、经过良好测试的库......
【解决方案3】:

请注意,此处提供的答案不会恢复 std::cout 的完整状态。例如,std::setfill 即使在调用.flags() 之后也会“粘住”。更好的解决方案是使用.copyfmt:

std::ios oldState(nullptr);
oldState.copyfmt(std::cout);

std::cout
    << std::hex
    << std::setw(8)
    << std::setfill('0')
    << 0xDECEA5ED
    << std::endl;

std::cout.copyfmt(oldState);

std::cout
    << std::setw(15)
    << std::left
    << "case closed"
    << std::endl;

将打印:

case closed

而不是:

case closed0000

【讨论】:

  • 虽然我最初的问题已经在几年前得到了回答,但这个答案是一个很好的补充。 :-)
  • @UltraInstinct 这似乎是一个更好的 解决方案,在这种情况下,您可以并且可能应该将其设为可接受的答案。
  • 如果为流启用了异常,由于某些原因,这会引发异常。 coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
  • 看来std::ios 总是处于bad 状态,因为它有NULL rdbuf。因此,设置启用异常的状态会导致由于状态不佳而引发异常。解决方案: 1) 使用带有rdbuf 的类(例如std::stringstream)而不是std::ios。 2) 将异常状态分别保存到局部变量并在state.copyfmt 之前禁用它们,然后从变量中恢复异常(并在从已禁用异常的oldState 恢复状态后再次执行此操作)。 3) 将rdbuf 设置为std::ios,如下所示:struct : std::streambuf {} sbuf; std::ios oldState(&amp;sbuf);
【解决方案4】:

我使用此答案中的示例代码创建了一个 RAII 类。如果您从一个在 iostream 上设置标志的函数有多个返回路径,那么这种技术的最大优势就来了。无论使用哪个返回路径,都将始终调用析构函数并且始终重置标志。函数返回时不会忘记恢复标志。

class IosFlagSaver {
public:
    explicit IosFlagSaver(std::ostream& _ios):
        ios(_ios),
        f(_ios.flags()) {
    }
    ~IosFlagSaver() {
        ios.flags(f);
    }

    IosFlagSaver(const IosFlagSaver &rhs) = delete;
    IosFlagSaver& operator= (const IosFlagSaver& rhs) = delete;

private:
    std::ostream& ios;
    std::ios::fmtflags f;
};

然后,只要您想保存当前标志状态,就可以通过创建 IosFlagSaver 的本地实例来使用它。当此实例超出范围时,标志状态将恢复。

void f(int i) {
    IosFlagSaver iosfs(std::cout);

    std::cout << i << " " << std::hex << i << " ";
    if (i < 100) {
        std::cout << std::endl;
        return;
    }
    std::cout << std::oct << i << std::endl;
}

【讨论】:

  • 太好了,如果有人扔了,你的信息流中仍然有正确的标志。
  • 除了标志之外,流状态还有更多内容。
  • 我真的希望 C++ 允许 try/finally。这是 RAII 工作的一个很好的例子,但最终会更简单。
  • 如果您的项目至少有点理智,那么您有 Boost 并且为此目的附带了 state savers。
【解决方案5】:

您可以在标准输出缓冲区周围创建另一个包装器:

#include <iostream>
#include <iomanip>
int main() {
    int x = 76;
    std::ostream hexcout (std::cout.rdbuf());
    hexcout << std::hex;
    std::cout << x << "\n"; // still "76"
    hexcout << x << "\n";   // "4c"
}

在函数中:

void print(std::ostream& os) {
    std::ostream copy (os.rdbuf());
    copy << std::hex;
    copy << 123;
}

当然,如果性能是一个问题,这会有点贵,因为它会复制整个 ios 对象(但不包括缓冲区),包括一些您付费但不太可能使用的东西,例如语言环境。

否则我觉得如果你要使用.flags(),最好保持一致并使用.setf(),而不是&lt;&lt;语法(纯粹的风格问题)。

void print(std::ostream& os) {
    std::ios::fmtflags os_flags (os.flags());
    os.setf(std::ios::hex);
    os << 123;
    os.flags(os_flags);
}

正如其他人所说,为了方便起见,您可以将上述内容(以及 .precision() 和 .fill(),但通常不是与语言环境和单词相关的内容,这些内容通常不会被修改并且更重)使其异常安全;构造函数应该接受std::ios&amp;。

【讨论】:

  • 好点[+],但它当然记得使用std::stringstream 格式化部分作为Mark Sherred pointed out。
  • @Wolf 我不确定我是否明白你的意思。 std::stringstream 是 std:ostream,除非使用一个会引入额外的中间缓冲区。
  • 当然,两者都是格式化输出的有效方法,都引入了一个流对象,你描述的那个对我来说是新的。我现在必须考虑利弊。然而,一个具有启发性答案的鼓舞人心的问题......(我的意思是流复制变体)
  • 您不能复制流,因为复制缓冲区通常没有意义(例如标准输出)。但是,您可以为同一个缓冲区拥有多个流对象,这就是这个答案所建议的。而std:stringstream 将创建自己的独立std:stringbuf(std::streambuf 派生类),然后需要将其倒入std::cout.rdbuf()
  • @n.caillou "The" std::ostream 实例通常由客户端提供(或者是全局的,例如std::cout),客户端/全局ostream 状态是需要的返回时保持/恢复。本地/自动 std::stringstream 允许您将状态调整隔离到本地 std::ostream,而不是操纵客户端 std::ostream(或 cout)的状态。
【解决方案6】:

稍作修改以使输出更具可读性:

void printHex(std::ostream& x) {
   ios::fmtflags f(x.flags());
   x << std::hex << 123 << "\n";
   x.flags(f);
}

int main() {
    std::cout << 100 << "\n"; // prints 100 base 10
    printHex(std::cout);      // prints 123 in hex
    std::cout << 73 << "\n";  // problem! prints 73 in hex..
}

【讨论】:

    【解决方案7】:

    C++20 std::format 在大多数情况下是保存恢复的绝佳替代方案

    一旦你可以使用它,你将例如能够将十六进制简单地写为:

    #include <format>
    #include <string>
    
    int main() {
        std::cout << std::format("{x} {#x} {}\n", 16, 17, 18);
    }
    

    预期输出:

    10 0x11 18
    

    因此这将完全克服修改std::cout状态的疯狂。

    更多信息请访问:C++ cout hex values?

    【讨论】:

    • 很高兴知道,但截至 2021 年 4 月,编译器不支持它,即使标准已经存在,也许在这个答案中值得一提。
    • 我非常期待这个库成为 C++ 编译器的标准。
    【解决方案8】:

    我想概括一下来自 qbert220 的答案:

    #include <ios>
    
    class IoStreamFlagsRestorer
    {
    public:
        IoStreamFlagsRestorer(std::ios_base & ioStream)
            : ioStream_(ioStream)
            , flags_(ioStream_.flags())
        {
        }
    
        ~IoStreamFlagsRestorer()
        {
            ioStream_.flags(flags_);
        }
    
    private:
        std::ios_base & ioStream_;
        std::ios_base::fmtflags const flags_;
    };
    

    这应该适用于输入流和其他流。

    PS:我很想对上面的答案做一个简单的评论,但是由于缺少声誉,stackoverflow 不允许我这样做。因此,让我在这里把答案弄得乱七八糟,而不是简单的评论......

    【讨论】:

      【解决方案9】:

      采用&lt;&lt; 和setf 和unsetf 代替将格式注入cout 可能是更清洁的解决方案。

      void printHex(std::ostream& x){
        x.setf(std::ios::hex, std::ios::basefield);
        x << 123;
        x.unsetf(std::ios::basefield);
      }
      

      ios_base 命名空间也可以正常工作

      void printHex(std::ostream& x){
        x.setf(std::ios_base::hex, std::ios_base::basefield);
        x << 123;
        x.unsetf(std::ios_base::basefield);
      }
      

      参考:http://www.cplusplus.com/reference/ios/ios_base/setf/

      【讨论】:

        猜你喜欢
        • 2013-05-16
        • 2019-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多