C++ 操作std::cout后恢复其状态

C++ 操作std::cout后恢复其状态,c++,iostream,C++,Iostream,假设我有这样一个代码: 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.. }

假设我有这样一个代码:

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..
}
void printHex(标准::ostream&x){
x这似乎正是您所需要的。:-)

基于代码段的示例:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}
void printHex(标准::ostream&x){
boost::io::ios\u flags\u saver ifs(x);
x您需要
#包括
#包括
,然后在需要时:

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

//Your code here...

cout.flags( f );

您可以将这些内容放在函数的开头和结尾,或者查看如何将其与一起使用。

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

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..
}
void printHex(标准::ostream&x){
ios::fmtflags f(x.flags());

x我已经使用这个答案中的示例代码创建了一个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;
}
void f(int i){
iosfs(标准::cout);

std::cout请注意,此处提供的答案不会恢复
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 closed0000

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

#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"
}

正如其他人所说,您可以将上述内容(以及
.precision()
.fill()
,但通常不包括通常不会修改且更重的与区域设置和单词相关的内容)在一个类中,为了方便并使其异常安全;构造函数应该接受
std::ios&

我想概括一下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_;
};
#包括
类IoStreamFlagsRestorer
{
公众:
IoStreamFlagsRestorer(标准::ios_base和ioStream)
:ioStream(ioStream)
,flags_389;(ioStream_389;.flags())
{
}
~IoStreamFlagsRestorer()
{
ioStream_u2;.flags(flags_2;);
}
私人:
std::ios_base和ioStream_;
std::ios_base::fmtflags const flags_u;
};
这应该适用于输入流和其他流


PS:我想把这仅仅作为对上述答案的一个评论,但是stackoverflow不允许我这样做,因为缺少声誉。因此,让我把答案弄得乱七八糟,而不是一个简单的评论…

std::format
在大多数情况下将是保存还原的一个更好的选择

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

#include <format>
#include <string>

int main() {
    std::cout << std::format("{x} {#x} {}\n", 16, 17, 18);
}
因此,这将完全克服修改
std::cout
状态的疯狂



有关详细信息,请访问:

我认为十六进制值只会在下一次移位操作中持续,而不是将格式注入cout。
我认为十六进制值只会在手动而不是使用操纵器更改格式标志时持续更改。@BillyONeal:不,使用操纵器与手动更改格式标志具有相同的效果。:-PIf您是here由于隐蔽查找未还原ostream格式(流格式状态)然后我做了类似的事情——看我的代码审查问题。这个问题是为什么iOFFROW不比STDIO更好的例子。因为没有/半/完全/什么不持久IOMANIP,发现了两个讨厌的bug。@克里斯蒂亚斯杨,实际上是好的C++是RAII,特别是在这种情况下!亚历克西斯I 100%同意。(Boost IO流状态保存程序)。:-)这不是异常安全的。除了标志之外,流状态还有更多内容。您可以通过不将格式推送到流中来避免问题。将格式和数据推送到一个临时stringstream变量中,然后打印优秀,如果有人抛出,您的流中仍然有正确的标志。除了标志之外,流状态还有更多内容。我是联盟希望C++允许尝试/最后。这是一个很好的例子,RAII工作,但最终会更简单。如果你的项目至少有点理智,你有提升,这就是为了这个目的。虽然我的原始问题已经回答了几年,这个答案是一个伟大的补充。更好的解决方案,在这种情况下,您可以而且可能应该将其作为可接受的答案。由于某些原因,如果流启用了异常,则会引发异常。似乎
std::ios
始终处于错误状态,因为它具有
NULL
rdbuf。因此,设置启用了异常的状态会导致异常引发错误错误状态的se。解决方案:1)使用设置了
rdbuf
的某个类(例如
std::stringstream
),而不是
std::ios
。2)将异常状态单独保存到局部变量,并在
state.copyfmt
之前禁用它们,然后从该变量还原异常(从禁用异常的
oldState
还原状态后再次执行此操作)。3)将
rdbuf
设置为
std::ios
,如下所示:
struct:std::streambuf{}sbuf;std::ios oldState(&sbuf)
请注意,这里没有魔法,
ios_flags_saver
基本上只是保存和设置标志,就像@StefanKendall的答案一样。@einpoklum但它是异常安全的,与其他答案不同。;-)除了标志之外,流状态还有更多。@jww IO流状态保存库有多个类,用于保存不同的部分流状态的s,其中的
ios\u flags\u saver
只是其中之一。如果您认为值得自己重新实现和维护每一件小事,而不是使用经过审核、经过良好测试的库……这一点很好[+],但它当然记得将其用作格式化部分。@Wol
void print(std::ostream& os) {
    std::ios::fmtflags os_flags (os.flags());
    os.setf(std::ios::hex);
    os << 123;
    os.flags(os_flags);
}
#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_;
};
#include <format>
#include <string>

int main() {
    std::cout << std::format("{x} {#x} {}\n", 16, 17, 18);
}
10 0x11 18
void printHex(std::ostream& x){
  x.setf(std::ios::hex, std::ios::basefield);
  x << 123;
  x.unsetf(std::ios::basefield);
}
void printHex(std::ostream& x){
  x.setf(std::ios_base::hex, std::ios_base::basefield);
  x << 123;
  x.unsetf(std::ios_base::basefield);
}