操作後に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

3
Covertiyの結果、ostream形式を復元しない(STREAM_FORMAT_STATE)のためにここにいる場合は、Coverityの検索結果:ostream形式を復元しない(STREAM_FORMAT_STATE)を参照してください。
jww 2016年

私は同様のことをしました-コードレビューで私の質問を参照してください:標準ストリームを使用し、後でその設定を復元します
Toby Speight 2018

1
この質問は、iostreamがstdioよりも優れていない理由の完璧な例です。not- / semi- / fully- / what-notの永続的なiomanipが原因で2つの厄介なバグが見つかりました。
fuujuhi

回答:


97

必要なとき、#include <iostream>または#include <ios>必要なときに:

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

//Your code here...

cout.flags( f );

これらを関数の最初と最後に配置するか、RAIIでこれを使用する方法についてこの回答を確認できます。


5
@ ChrisJester-Young、実際に良いC ++はRAIIで、特にこのような場合には!
Alexis Wilke、2015

4
@アレクシス私は100%同意します。私の答え(Boost IO Stream State Saver)を参照してください。:-)
Chris Jester-Young

3
これは例外セーフではありません。
einpoklum 2016

2
フラグの他に、ストリームの状態には他にもあります。
jww 2017年

3
ストリームにフォーマットをプッシュしないことで問題を回避できます。形式とデータを一時的なstringstream変数にプッシュしてから印刷します
Mark Sherred

63

ブーストIOストリーム州立セーバーは、あなたが必要なものを正確に思えます。:-)

コードスニペットに基づく例:

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

1
ここには魔法がないことに注意してください。ios_flags_saver基本的には、@ StefanKendallの回答のようにフラグを保存して設定するだけです。
einpoklum 2016

15
@einpoklumしかし、他の回答とは異なり、例外に対して安全です。;-)
Chris Jester-Young、

2
フラグの他に、ストリームの状態には他にもあります。
jww 2017年

4
@jww IOストリーム状態セーバーライブラリには、ストリーム状態のさまざまな部分を保存するための複数のクラスがあり、ios_flags_saverそのうちの1つだけです。
Chris Jester-Young

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 2015年

2
@UltraInstinctそれはより良い解決策であるように見えます。その場合、代わりにそれを受け入れられた答えにすることができ、おそらくすべきです。
underscore_d

ストリームで例外が有効になっている場合、これは何らかの理由で例外をスローします。coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh 2018

1
それはそうstd::iosでは常に悪い、それは持っているので、状態NULLRDBUF。したがって、例外を有効にして状態を設定すると、状態が悪いために例外がスローされます。解決策:1)の代わりにstd::stringstreamrdbufセットのあるクラス(たとえば)を使用しstd::iosます。2)例外状態をローカル変数に個別に保存し、前state.copyfmtに無効にしてから、変数から例外を復元します(oldState例外が無効になっている状態を復元した後、これを再度実行します)。3)次のように設定rdbufしますstd::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
すばらしい。誰かがスローしても、ストリームに正しいフラグが残っている。
Alexis Wilke、2015

4
フラグの他に、ストリームの状態には他にもあります。
jww 2017年

1
私は本当にC ++がtry / finallyを許可することを望みます。これはRAIIが機能する優れた例ですが、最終的にはもっと簡単になります。
貿易アイデアフィリップ

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()and .fill()、ただし通常は変更されず、より重いロケールおよび単語に関連するものではない)を便宜上、例外に安全にするためにクラスに入れることができます。コンストラクタは受け入れる必要がありますstd::ios&ます。


良い点[+]ですが、もちろんstd::stringstreamマークシェレッドが指摘したように、書式設定部分に使用することを覚えています。
ウルフ

@オオカミ私はあなたのポイントを取得するかどうかはわかりません。Anがstd::stringstream あるstd:ostream 1本の紹介余分な中間バッファを使用することを除いて、。
n.caillou

もちろん、どちらも出力をフォーマットする有効な方法であり、どちらもストリームオブジェクトを導入します。今、長所と短所について考えなければなりません。ただし、啓発的な回答を伴う刺激的な質問...(ストリームコピーのバリアントを意味します)
Wolf

1
ストリームをコピーすることはできません。バッファをコピーしても意味がないことが多いためです(例:stdout)。ただし、同じバッファに複数のストリームオブジェクトを設定することができます。これは、この回答で提案されていることです。一方、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は評判がないためにそうすることを許可しません。したがって、単純なコメントではなく、ここで答えを散らかします...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.