操作后恢复std :: cout的状态


105

假设我有这样的代码:

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::boolalphastd::noboolalpha..)?

谢谢。


我相信十六进制只会在下一次移出操作中持续。仅当您手动更改格式标志而不是使用操纵器时,更改才是持久的。
Billy ONeal 2010年

4
@BillyONeal:否,使用操纵器与手动更改格式标志具有相同的效果。:-P
Chris Jester-Young 2010年

3
如果您因Covertiy发现而未恢复ostream格式(STREAM_FORMAT_STATE),则请参见Coverity发现:未恢复ostream格式(STREAM_FORMAT_STATE)
jww

我做了类似的事情-在代码审查中看到我的问题:使用标准流,然后恢复其设置
Toby Speight,

1
这个问题是iostream为什么不比stdio更好的完美例子。刚刚发现了两个讨厌的bug,因为它们不是//半/完全//不是什么持久的iomanip。
fuujuhi

Answers:


97

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

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

//Your code here...

cout.flags( f );

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


5
@ ChrisJester-Young,实际上好的C ++是RAII,尤其是在这种情况下!
亚历克西斯·威尔克

4
@Alexis我100%同意。请参阅我的答案(提升IO流状态保护程序)。:-)
克里斯·杰斯特·杨

3
这不是异常安全的。
einpoklum '16

2
除了标志之外,流状态还有更多。
jww

3
您可以通过不将格式推送到流中来避免此问题。将格式和数据压入一个临时的stringstream变量,然后打印
Mark Sherred '18

63

升压IO流状态节电器似乎正是你需要的。:-)

基于您的代码段的示例:

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

1
请注意,这里没有魔术,ios_flags_saver基本上只是像@StefanKendall的答案一样保存和设置标志。
einpoklum '16

15
@einpoklum但这是异常安全的,与其他答案不同。;-)
克里斯·杰斯特·杨

2
除了标志之外,流状态还有更多。
jww

4
@jww IO流状态保护程序库具有多个类,用于保存流状态的不同部分,其中ios_flags_saver只有一个。
克里斯·杰斯特·杨

3
如果您认为值得自己重新实现和维护每件事,而不是使用经过审查且经过良好测试的库……
jupp0r

45

请注意,此处提供的答案不会恢复的完整状态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

2
@UltraInstinct这似乎是一个更好的解决方案,在这种情况下,您可以并且可能应该使其成为可接受的答案。
underscore_d

如果为流启用了异常,出于某些原因,这将引发异常。coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh,

1
由于它具有rdbuf ,因此似乎std::ios始终处于不良状态NULL。因此,设置一个启用了异常的状态会由于状态错误而引发异常。解决方案:1)使用std::stringstream带有rdbufset而不是的某些类(例如)std::ios。2)将异常状态单独保存到局部变量中,并在之前state.copyfmt将其禁用,然后从变量中恢复异常(并在从中oldState禁用了异常的状态恢复后再次执行此操作)。3)设置rdbufstd::iosstruct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh,

22

我使用此答案中的示例代码创建了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
太好了,如果有人扔了,您的流中仍然有正确的标志。
亚历克西斯·威尔克

4
除了标志之外,流状态还有更多。
jww

1
我真的希望C ++允许尝试/最终尝试。这是RAII工作的一个很好的例子,但最终会更简单。
Trade-Ideas Philip

2
如果您的项目至少有些理智,则可以使用Boost,并且为此提供了状态保护程序
Jan Hudec

9

进行一些修改以使输出更具可读性:

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

9

您可以在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&


好点[+],但是它当然记得std::stringstreamMark Sherred所指出的那样用于格式化部分。

@狼我不确定我是否明白你的意思。An std::stringstream 一个std:ostream,除了使用会引入一个额外的中间缓冲区。
n.caillou

当然,两种都是格式化输出的有效方法,都引入了流对象,您所描述的对我来说是新的。我现在必须考虑利弊。但是,这是一个令人鼓舞的问题,具有启发性的答案...(我是指流复制变体)
Wolf

1
您无法复制流,因为复制缓冲区通常没有意义(例如stdout)。但是,对于同一个缓冲区,您可以有多个流对象,这是此答案建议执行的操作。而an std:stringstream将创建自己的独立std:stringbufstd::streambuf衍生产品),然后需要将其倒入std::cout.rdbuf()
n.caillou

感谢您的澄清。

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.