Kembalikan status std :: cout setelah memanipulasinya

105

Misalkan saya memiliki kode seperti ini:

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

Pertanyaan saya adalah apakah ada cara untuk 'mengembalikan' keadaan coutke semula setelah kembali dari fungsi? (Agak suka std::boolalphadan std::noboolalpha..)?

Terima kasih.

UltraInstinct
sumber
Saya percaya hex hanya bertahan untuk operasi shift out berikutnya. Perubahan ini hanya terjadi jika Anda mengubah format bendera secara manual daripada menggunakan manipulator.
Billy ONeal
4
@BillyONeal: Tidak, menggunakan manipulator memiliki efek yang sama seperti mengubah tanda format secara manual. :-P
Chris Jester-Young
3
Jika Anda berada di sini karena temuan Covertiy Tidak memulihkan format ostream (STREAM_FORMAT_STATE) , lihat Temuan Coverity: Tidak memulihkan format ostream (STREAM_FORMAT_STATE) .
jww
Saya melakukan hal serupa - lihat pertanyaan saya di Code Review: Gunakan aliran standar, dan pulihkan pengaturannya setelah itu .
Toby Speight
1
Pertanyaan ini adalah contoh sempurna mengapa iostream tidak lebih baik dari stdio. Baru saja menemukan dua bug jahat karena iomanip tidak- / semi- / fully- / what-not persisten.
fuujuhi

Jawaban:

97

Anda perlu #include <iostream>atau #include <ios>saat diminta:

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

//Your code here...

cout.flags( f );

Anda dapat meletakkan ini di awal dan akhir fungsi Anda, atau lihat jawaban tentang cara menggunakan ini dengan RAII .

Stefan Kendall
sumber
5
@ ChrisJester-Young, sebenarnya C ++ yang bagus adalah RAII, terutama dalam kasus seperti ini!
Alexis Wilke
4
@Alexis Saya 100% setuju. Lihat jawaban saya (Boost IO Stream State Saver). :-)
Chris Jester-Young
3
Ini bukan pengecualian-aman.
einpoklum
2
Ada lebih banyak status aliran selain bendera.
jww
3
Anda dapat menghindari masalah dengan tidak memasukkan format ke aliran. Dorong format dan data ke dalam variabel
aliran string
63

The Meningkatkan IO Streaming Negara Saver tampaknya apa yang Anda butuhkan. :-)

Contoh berdasarkan cuplikan kode Anda:

void printHex(std::ostream& x) {
    boost::io::ios_flags_saver ifs(x);
    x << std::hex << 123;
}
Chris Jester-Young
sumber
1
Perhatikan bahwa tidak ada keajaiban di sini, yang ios_flags_saverpada dasarnya hanya menyimpan dan menyetel bendera seperti dalam jawaban @ StefanKendall.
einpoklum
15
@einpoklum Tapi ini pengecualian-aman, tidak seperti jawaban lainnya. ;-)
Chris Jester-Young
2
Ada lebih banyak status aliran selain bendera.
jww
4
@jww Pustaka IO Stream State Saver memiliki beberapa kelas, untuk menyimpan bagian berbeda dari status streaming, yang mana ios_flags_saverhanya satu.
Chris Jester-Young
3
Jika menurut Anda layak untuk menerapkan ulang dan memelihara setiap hal kecil sendiri, daripada menggunakan perpustakaan yang telah ditinjau dan diuji dengan baik ...
jupp0r
45

Perhatikan bahwa jawaban yang disajikan di sini tidak akan memulihkan keadaan penuh std::cout. Misalnya, std::setfillakan "tetap" bahkan setelah menelepon .flags(). Solusi yang lebih baik adalah dengan menggunakan .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;

Akan mencetak:

case closed

daripada:

case closed0000
rr-
sumber
Meskipun pertanyaan awal saya telah dijawab beberapa tahun yang lalu, jawaban ini merupakan tambahan yang bagus. :-)
UltraInstinct
2
@UltraInstinct Tampaknya ini adalah solusi yang lebih baik , dalam hal ini, Anda dapat dan mungkin harus menjadikannya sebagai jawaban yang diterima.
underscore_d
Ini karena beberapa alasan memunculkan pengecualian jika pengecualian diaktifkan untuk streaming. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh
1
Tampaknya std::iosselalu dalam keadaan buruk karena memiliki NULLrdbuf. Jadi menyetel keadaan dengan pengecualian diaktifkan menyebabkan pengecualian melempar karena keadaan buruk. Solusi: 1) Gunakan beberapa kelas (misalnya std::stringstream) dengan rdbufset, bukan std::ios. 2) Simpan status pengecualian secara terpisah ke variabel lokal dan nonaktifkan sebelumnya state.copyfmt, lalu pulihkan pengecualian dari variabel (dan lakukan ini lagi setelah memulihkan status oldStateyang pengecualiannya dinonaktifkan). 3) Set rdbufke std::iosseperti ini:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh
22

Saya telah membuat kelas RAII menggunakan kode contoh dari jawaban ini. Keuntungan besar dari teknik ini datang jika Anda memiliki beberapa jalur pengembalian dari fungsi yang menyetel bendera pada iostream. Jalur pengembalian mana pun yang digunakan, destruktor akan selalu dipanggil dan bendera akan selalu disetel ulang. Tidak ada kemungkinan lupa untuk memulihkan bendera saat fungsi kembali.

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

Anda kemudian akan menggunakannya dengan membuat instance lokal IosFlagSaver kapan pun Anda ingin menyimpan status flag saat ini. Ketika instance ini keluar dari ruang lingkup, status bendera akan dipulihkan.

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;
}
qbert220.dll
sumber
2
Luar biasa, jika seseorang melempar, Anda masih mendapatkan panji yang benar di aliran Anda.
Alexis Wilke
4
Ada lebih banyak status aliran selain bendera.
jww
1
Saya sangat berharap C ++ diizinkan mencoba / akhirnya. Ini adalah contoh yang sangat baik di mana RAII bekerja, tetapi pada akhirnya akan lebih sederhana.
Ide-Dagang Philip
2
Jika proyek Anda setidaknya sedikit waras, Anda memiliki Boost dan itu dilengkapi dengan penyelamat negara untuk tujuan ini.
Jan Hudec
9

Dengan sedikit modifikasi untuk membuat keluarannya lebih mudah dibaca:

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..
}
whacko__Cracko
sumber
9

Anda dapat membuat pembungkus lain di sekitar buffer 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"
}

Dalam sebuah fungsi:

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

Tentu saja jika kinerja menjadi masalah, ini sedikit lebih mahal karena menyalin seluruh iosobjek (tetapi bukan buffer) termasuk beberapa hal yang Anda bayar tetapi tidak mungkin digunakan seperti lokal.

Kalau tidak, saya merasa jika Anda akan menggunakannya .flags()lebih baik untuk konsisten dan menggunakan .setf()juga daripada <<sintaks (pertanyaan murni tentang gaya).

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

Seperti yang dikatakan orang lain, Anda dapat meletakkan hal-hal di atas (dan .precision()dan .fill(), tetapi biasanya bukan lokal dan hal-hal yang berhubungan dengan kata-kata yang biasanya tidak akan dimodifikasi dan lebih berat) di dalam kelas untuk kenyamanan dan membuatnya aman untuk pengecualian; konstruktor harus menerima std::ios&.

n.caillou
sumber
Poin bagus [+], tapi tentu saja ingat untuk digunakan std::stringstreamuntuk bagian pemformatan seperti yang ditunjukkan Mark Sherred .
Serigala
@ Wolf, saya tidak yakin saya mengerti maksud Anda. Sebuah std::stringstream adalah sebuah std:ostream, kecuali menggunakan salah memperkenalkan buffer ekstra menengah.
n.caillou
Tentu saja keduanya merupakan pendekatan yang valid untuk memformat keluaran, keduanya memperkenalkan objek aliran, yang Anda jelaskan baru bagi saya. Saya harus memikirkan pro dan kontra sekarang. Namun, pertanyaan yang menginspirasi dengan jawaban yang mencerahkan ... (maksud saya varian salinan streaming)
Wolf
1
Anda tidak dapat menyalin aliran, karena menyalin buffer sering kali tidak masuk akal (mis. Stdout). Namun, Anda dapat memiliki beberapa objek aliran untuk buffer yang sama, yang diusulkan oleh jawaban ini. Sedangkan std:stringstreamwasiat membuat sendiri std:stringbuf( std::streambufturunan), yang kemudian perlu dituangkan kestd::cout.rdbuf()
n.caillou
Terimakasih atas klarifikasinya.
Serigala
0

Saya ingin menggeneralisasi jawaban dari 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_;
};

Ini harus berfungsi untuk aliran input dan lainnya juga.

PS: Saya ingin membuat ini hanya sebagai komentar untuk jawaban di atas, namun stackoverflow tidak mengizinkan saya melakukannya karena reputasi yang hilang. Jadi buat saya mengacaukan jawaban di sini daripada komentar sederhana ...

J. Wilde
sumber