Khôi phục trạng thái của std :: cout sau khi thao tác với nó


105

Giả sử tôi có một đoạn mã như sau:

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

Câu hỏi của tôi là liệu có cách nào để 'khôi phục' trạng thái về trạng thái coutban đầu sau khi trở về từ chức năng không? (Hơi giống std::boolalphastd::noboolalpha..)?

Cảm ơn.


Tôi tin rằng hex chỉ tồn tại cho hoạt động thay đổi tiếp theo. Sự thay đổi chỉ tồn tại nếu bạn thay đổi các cờ định dạng theo cách thủ công thay vì sử dụng trình thao tác.
Billy ONeal

4
@BillyONeal: Không, sử dụng trình thao tác có tác dụng tương tự như thay đổi cờ định dạng theo cách thủ công. :-P
Chris Jester-Young

3
Nếu bạn ở đây do phát hiện của Covertiy Không khôi phục định dạng ostream (STREAM_FORMAT_STATE) , hãy xem Phát hiện của Coverity: Không khôi phục định dạng ostream (STREAM_FORMAT_STATE) .
jww

Tôi đã làm điều gì đó tương tự - xem câu hỏi của tôi trên Đánh giá mã: Sử dụng một luồng tiêu chuẩn và khôi phục cài đặt của luồng sau đó .
Toby Speight

1
Câu hỏi này là một ví dụ hoàn hảo về lý do tại sao iostream không tốt hơn stdio. Chỉ tìm thấy hai lỗi khó chịu vì không phải- / bán- / đầy đủ- / cái gì-không liên tục iomanip.
fuujuhi

Câu trả lời:


97

bạn cần #include <iostream>hoặc #include <ios>sau đó khi được yêu cầu:

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

//Your code here...

cout.flags( f );

Bạn có thể đặt chúng ở đầu và cuối hàm của mình hoặc xem câu trả lời này về cách sử dụng nó với RAII .


5
@ ChrisJester-Young, C ++ thực sự tốt là RAII, đặc biệt là trong trường hợp như thế này!
Alexis Wilke

4
@Alexis Tôi 100% đồng ý. Xem câu trả lời của tôi (Boost IO Stream State Saver). :-)
Chris Jester-Young

3
Nó không phải là ngoại lệ an toàn.
einpoklum

2
Còn nhiều thứ khác đối với trạng thái luồng ngoài cờ.
jww

3
Bạn có thể tránh sự cố bằng cách không đẩy các định dạng vào luồng. Đẩy định dạng và dữ liệu vào một biến stringstream tạm thời, sau đó in
Đánh dấu Sherred

63

Các Boost IO Suối nước Saver dường như chính xác những gì bạn cần. :-)

Ví dụ dựa trên đoạn mã của bạn:

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

1
Lưu ý rằng không có phép thuật nào ở đây, ios_flags_savervề cơ bản chỉ lưu và đặt các cờ như trong câu trả lời của @ StefanKendall.
einpoklum

15
@einpoklum Nhưng nó là ngoại lệ an toàn, không giống như câu trả lời khác. ;-)
Chris Jester-Young

2
Còn nhiều thứ khác đối với trạng thái luồng ngoài cờ.
jww

4
@jww Thư viện IO Stream State Saver có nhiều lớp, để lưu các phần khác nhau của trạng thái luồng, trong đó ios_flags_saverchỉ là một.
Chris Jester-Young

3
Nếu bạn nghĩ rằng nó đáng reimplementing và duy trì tất cả mọi thứ một mình, thay vì sử dụng một thư viện cũng được thử nghiệm được đánh giá ...
jupp0r

45

Lưu ý rằng các câu trả lời được trình bày ở đây sẽ không khôi phục trạng thái đầy đủ của std::cout. Ví dụ: std::setfillsẽ "dính" ngay cả sau khi gọi .flags(). Một giải pháp tốt hơn là sử dụng .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;

Sẽ in:

case closed

hơn là:

case closed0000

Mặc dù câu hỏi ban đầu của tôi đã được trả lời cách đây vài năm, nhưng câu trả lời này là một bổ sung tuyệt vời. :-)
UltraInstinct

2
@UltraInstinct Nó có vẻ là một giải pháp tốt hơn , trong trường hợp đó, bạn có thể và có thể nên biến nó thành câu trả lời được chấp nhận.
underscore_d

Điều này vì một số lý do sẽ ném ra ngoại lệ nếu ngoại lệ được bật cho luồng. coliru.stacked-crooked.com/a/2a4ce6f5d3d8925b
anton_rh 29/12/18

1
Có vẻ như nó std::iosluôn ở trạng thái xấu vì nó có NULLrdbuf. Vì vậy, việc thiết lập trạng thái có kích hoạt ngoại lệ sẽ gây ra hiện tượng ném ngoại lệ do trạng thái xấu. Giải pháp: 1) Sử dụng một số lớp (ví dụ std::stringstream) với rdbuftập hợp thay vì std::ios. 2) Lưu trạng thái ngoại lệ riêng biệt vào biến cục bộ và tắt chúng trước state.copyfmtđó, sau đó khôi phục ngoại lệ từ biến (và thực hiện lại điều này sau khi khôi phục trạng thái oldStateđã tắt ngoại lệ từ đó). 3) Đặt rdbufthành std::iosthích thế này:struct : std::streambuf {} sbuf; std::ios oldState(&sbuf);
anton_rh

22

Tôi đã tạo một lớp RAII bằng cách sử dụng mã ví dụ từ câu trả lời này. Lợi thế lớn của kỹ thuật này là nếu bạn có nhiều đường dẫn trả về từ một hàm đặt cờ trên iostream. Cho dù sử dụng đường dẫn trả về nào, hàm hủy sẽ luôn được gọi và các cờ sẽ luôn được đặt lại. Không có khả năng quên khôi phục các cờ khi hàm trả về.

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

Sau đó, bạn sẽ sử dụng nó bằng cách tạo một phiên bản cục bộ của IosFlagSaver bất cứ khi nào bạn muốn lưu trạng thái cờ hiện tại. Khi trường hợp này vượt ra khỏi phạm vi, trạng thái cờ sẽ được khôi phục.

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
Tuyệt vời, nếu ai đó ném, bạn vẫn có đúng cờ trong luồng của mình.
Alexis Wilke

4
Còn nhiều thứ khác đối với trạng thái luồng ngoài cờ.
jww

1
Tôi thực sự muốn C ++ được phép thử / cuối cùng. Đây là một ví dụ tuyệt vời mà RAII hoạt động, nhưng cuối cùng sẽ đơn giản hơn.
Trade-Ideas Philip

2
Nếu dự án của bạn ít nhất là một chút lành mạnh, bạn có Boost và đi kèm với trình tiết kiệm trạng thái cho mục đích này.
Jan Hudec

9

Với một chút sửa đổi để làm cho đầu ra dễ đọc hơn:

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

Bạn có thể tạo một trình bao bọc khác xung quanh bộ đệm 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"
}

Trong một hàm:

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

Tất nhiên nếu hiệu suất là một vấn đề, điều này sẽ đắt hơn một chút vì nó sao chép toàn bộ ios đối tượng (nhưng không phải bộ đệm) bao gồm một số thứ mà bạn đang trả tiền nhưng không có khả năng sử dụng như ngôn ngữ.

Nếu không, tôi cảm thấy nếu bạn định sử dụng .flags()thì tốt hơn là nên nhất quán và sử dụng .setf()tốt hơn là <<cú pháp (câu hỏi thuần túy về phong cách).

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

Như những người khác đã nói, bạn có thể đặt những thứ ở trên (và .precision().fill(), nhưng thường không phải là ngôn ngữ và những thứ liên quan đến từ thường sẽ không được sửa đổi và nặng hơn) trong một lớp để thuận tiện và an toàn cho ngoại lệ; hàm tạo nên chấp nhận std::ios&.


Điểm tốt [+], nhưng nó tất nhiên nhớ sử dụng std::stringstreamcho phần định dạng như Mark Sherred đã chỉ ra .
Wolf,

@Wolf Tôi không chắc mình hiểu ý bạn. An std::stringstream một std:ostream, ngoại trừ việc sử dụng một giới thiệu một bộ đệm trung gian bổ sung.
n.caillou

Tất nhiên cả hai đều là cách tiếp cận hợp lệ để định dạng đầu ra, cả hai đều giới thiệu một đối tượng luồng, đối tượng mà bạn mô tả là mới đối với tôi. Bây giờ tôi đang nghĩ về ưu và nhược điểm. Tuy nhiên, một câu hỏi truyền cảm hứng với giác ngộ câu trả lời ... (Tôi có nghĩa là các biến thể dòng copy)
Wolf

1
Bạn không thể sao chép luồng, vì sao chép bộ đệm thường không có ý nghĩa (ví dụ: stdout). Tuy nhiên, bạn có thể có một số đối tượng luồng cho cùng một bộ đệm, đó là điều mà câu trả lời này đề xuất làm. Trong khi đó, một std:stringstreamsẽ tạo ra độc lập riêng của mình std:stringbuf(một std::streambufderivate), sau đó cần phải được đổ vàostd::cout.rdbuf()
n.caillou

Cảm ơn bạn đã làm rõ.
Wolf

0

Tôi muốn khái quát câu trả lời từ qbert220 phần nào:

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

Điều này sẽ hoạt động cho các luồng đầu vào và những luồng khác.

Tái bút: Tôi muốn làm điều này chỉ đơn giản là một nhận xét cho câu trả lời ở trên, tuy nhiên stackoverflow không cho phép tôi làm như vậy vì thiếu danh tiếng. Vì vậy, làm cho tôi lộn xộn các câu trả lời ở đây thay vì một nhận xét đơn giản ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.