操作 std::cout 后恢复状态

Restore the state of std::cout after manipulating it

提问人:UltraInstinct 提问时间:2/16/2010 最后编辑:leiycUltraInstinct 更新时间:9/4/2022 访问量:62365

问:

假设我有一个这样的代码:

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..
}

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

谢谢。

C++ IOstream

评论

0赞 Billy ONeal 2/16/2010
我相信十六进制只持续到下一次移出操作。仅当手动更改格式标志而不是使用操纵器时,更改才会持续存在。
5赞 C. K. Young 2/16/2010
@BillyONeal:不,使用操纵器与手动更改格式标志具有相同的效果。9-3
3赞 jww 1/26/2016
如果您因 Covertiy 发现未恢复 ostream 格式 (STREAM_FORMAT_STATE) 而在此处,请参阅 Coverity 发现:未恢复 ostream 格式 (STREAM_FORMAT_STATE)。
0赞 Toby Speight 8/29/2018
我做了类似的事情 - 请参阅我在代码审查上的问题:使用标准流,然后恢复其设置
2赞 fuujuhi 12/16/2019
这个问题是一个完美的例子,说明为什么 iostream 不比 stdio 好。刚刚发现了两个讨厌的错误,因为不是-/半-/完全-/什么不是持久的iomanip。

答:

63赞 C. K. Young 2/16/2010 #1

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

基于代码片段的示例:

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

评论

1赞 einpoklum 2/28/2016
请注意,这里没有魔法,基本上只是保存和设置标志,就像@StefanKendall的答案一样。ios_flags_saver
17赞 C. K. Young 2/28/2016
@einpoklum 但它是例外安全的,不像其他答案。;-)
2赞 jww 3/18/2017
除了标志之外,流状态还有更多内容。
4赞 C. K. Young 3/19/2017
@jww IO 流状态保护程序库具有多个类,用于保存流状态的不同部分,其中只有一个。ios_flags_saver
3赞 jupp0r 2/6/2018
如果您认为值得自己重新实现和维护每一件小事,而不是使用经过审查、经过良好测试的库......
130赞 Stefan Kendall 2/16/2010 #2

您需要或在需要时:#include <iostream>#include <ios>

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

//Your code here...

cout.flags( f );

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

评论

5赞 Alexis Wilke 2/16/2015
@ChrisJester-Young,其实好C++是RAII,尤其是在这样的情况下!
4赞 C. K. Young 2/16/2015
@Alexis 我 100% 同意。请参阅我的答案(Boost IO Stream State Saver)。:-)
3赞 einpoklum 2/28/2016
这并非例外安全。
5赞 jww 3/18/2017
除了标志之外,流状态还有更多内容。
5赞 Mark Sherred 2/2/2018
您可以通过不将格式推送到流上来避免该问题。将格式和数据推送到临时字符串流变量中,然后打印
9赞 whacko__Cracko 2/16/2010 #3

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

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..
}
24赞 qbert220 9/16/2013 #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;
}

评论

2赞 Alexis Wilke 2/16/2015
太好了,如果有人投掷,你的直播中仍然有正确的标志。
4赞 jww 3/18/2017
除了标志之外,流状态还有更多内容。
1赞 Trade-Ideas Philip 4/19/2017
我真的希望C++允许尝试/最后。这是 RAII 工作的一个很好的例子,但最终会更简单。
2赞 Jan Hudec 6/13/2019
如果你的项目至少有一点理智,你有 Boost,它为此目的附带了状态保护程序
84赞 rr- 6/22/2015 #5

请注意,此处提供的答案不会恢复 的完整状态。例如,即使在调用后也会“粘”.更好的解决方案是使用:std::coutstd::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

评论

0赞 UltraInstinct 6/22/2015
虽然我最初的问题在几年前就已经得到了解答,但这个答案是一个很好的补充。:-)
8赞 underscore_d 9/30/2018
@UltraInstinct 这似乎是一个更好的解决方案,在这种情况下,你可以而且可能应该让它成为公认的答案。
0赞 anton_rh 12/29/2018
由于某些原因,如果为流启用了异常,则会引发异常。coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
1赞 anton_rh 1/9/2019
似乎总是处于不良状态,因为它有 rdbuf。因此,设置启用了异常的状态会导致由于状态错误而引发异常。解决方法:1)使用一些类(例如)与set而不是。2) 将异常状态单独保存到局部变量中,并在之前禁用它们,然后从变量中恢复异常(并在恢复禁用异常的状态后再次执行此操作)。3) 设置为如下所示:std::iosNULLstd::stringstreamrdbufstd::iosstate.copyfmtoldStaterdbufstd::iosstruct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
14赞 n.caillou 5/4/2017 #6

您可以在 stdout 缓冲区周围创建另一个包装器:

#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()<<

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&

评论

0赞 Wolf 9/17/2019
好点[+],但它当然记得使用 std::stringstream 作为格式部分,正如 Mark Sherred 指出的那样。
0赞 n.caillou 9/18/2019
@Wolf我不确定我是否明白你的意思。An ,但使用 one 会引入额外的中间缓冲区。std::stringstreamstd:ostream
0赞 Wolf 9/18/2019
当然,这两种方法都是格式化输出的有效方法,都引入了一个流对象,您描述的对象对我来说是新的。我现在必须考虑利弊。然而,一个鼓舞人心的问题和启发性的答案......(我的意思是流复制变体)
2赞 n.caillou 9/21/2019
您无法复制流,因为复制缓冲区通常没有意义(例如 stdout)。但是,同一缓冲区可以有多个流对象,这就是本答案建议执行的操作。而一个意志创造了自己的独立(一个派生),然后需要注入std:stringstreamstd:stringbufstd::streambufstd::cout.rdbuf()
1赞 n.caillou 3/19/2022
@franji1同样,您可以在不创建 stringbuf 的情况下做到这一点。你还没有明白答案;流和缓冲区是分开的东西
0赞 J. Wilde 8/9/2020 #7

我想从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不允许我这样做。因此,让我在这里把答案弄得一团糟,而不是简单的评论......

10赞 Ciro Santilli OurBigBook.com 9/24/2020 #8

在大多数情况下,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

现有库在获得官方支持之前实现它: https://github.com/fmtlib/fmt 在 Ubuntu 22.04 上安装:fmt

sudo apt install libfmt-dev

修改源以替换:

  • <format><fmt/core.h>
  • std::formatfmt::format

主 .cpp

#include <iostream>

#include <fmt/core.h>

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

并使用以下命令进行编译和运行:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

输出:

10 0x11 18

相关: std::string 格式,如 sprintf

评论

3赞 VP. 3/30/2021
很高兴知道,但截至 2021 年 4 月,编译器不支持它,即使标准在那里,也许在这个答案中值得一提。
1赞 shuhalo 5/12/2021
我非常期待这个库成为 C++ 编译器的标准交付。
0赞 Wolf 6/9/2023
同时,标准已经出台,支持它的实现也已经出台。我经历过的最令人印象深刻的事情是编译时错误表明格式字符串和参数之间不匹配。
1赞 aGuegu 6/12/2021 #9

与其将格式注入 cout,不如采用和可能是一个更干净的解决方案。<<setfunsetf

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/