Ai đó có thể vui lòng giải thích (tốt hơn là sử dụng tiếng Anh đơn giản) cách std::flush
hoạt động không?
- Nó là gì?
- Khi nào bạn xả một luồng?
- Tại sao nó lại quan trọng?
Cảm ơn bạn.
Câu trả lời:
Vì nó không được giải đáp điều gì std::flush
sẽ xảy ra, đây là một số chi tiết về nó thực sự là gì. std::flush
là một thao tác , tức là, một chức năng với một chữ ký cụ thể. Để bắt đầu đơn giản, bạn có thể nghĩ std::flush
đến việc có chữ ký
std::ostream& std::flush(std::ostream&);
Tuy nhiên, thực tế phức tạp hơn một chút (nếu bạn quan tâm, nó cũng được giải thích bên dưới).
Các toán tử đầu ra quá tải lớp luồng lấy các toán tử của dạng này, tức là, có một hàm thành viên lấy một trình thao tác làm đối số. Toán tử đầu ra gọi trình thao tác với chính đối tượng:
std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
(*manip)(*this);
return *this;
}
Tức là, khi bạn "xuất" std::flush
với tới an std::ostream
, nó chỉ gọi hàm tương ứng, tức là hai câu lệnh sau là tương đương:
std::cout << std::flush;
std::flush(std::cout);
Bây giờ, std::flush()
bản thân nó khá đơn giản: Tất cả những gì nó làm là gọi std::ostream::flush()
, tức là, bạn có thể hình dung cách triển khai của nó trông giống như sau:
std::ostream& std::flush(std::ostream& out) {
out.flush();
return out;
}
Về std::ostream::flush()
mặt kỹ thuật, hàm này sẽ gọi std::streambuf::pubsync()
bộ đệm luồng (nếu có) được liên kết với luồng: Bộ đệm luồng chịu trách nhiệm đệm các ký tự và gửi các ký tự đến đích bên ngoài khi bộ đệm được sử dụng sẽ tràn hoặc khi biểu diễn bên trong phải được đồng bộ hóa với đích bên ngoài, tức là, khi dữ liệu được xóa. Trên một luồng tuần tự, đồng bộ hóa với đích bên ngoài chỉ có nghĩa là mọi ký tự trong bộ đệm sẽ được gửi ngay lập tức. Đó là, việc sử dụng std::flush
khiến bộ đệm luồng làm sạch bộ đệm đầu ra của nó. Ví dụ: khi dữ liệu được ghi vào bảng điều khiển tuôn ra khiến các ký tự xuất hiện tại điểm này trên bảng điều khiển.
Điều này có thể đặt ra câu hỏi: Tại sao các ký tự không được viết ngay lập tức? Câu trả lời đơn giản là các ký tự viết thường khá chậm. Tuy nhiên, lượng thời gian cần thiết để viết một lượng ký tự hợp lý về cơ bản giống với việc chỉ viết một ký tự ở đâu. Số lượng ký tự phụ thuộc vào nhiều đặc điểm của hệ điều hành, hệ thống tệp, v.v. nhưng thường có tới 4k ký tự được viết trong khoảng thời gian chỉ bằng một ký tự. Do đó, việc đệm các ký tự trước khi gửi chúng bằng cách sử dụng bộ đệm tùy thuộc vào chi tiết của đích bên ngoài có thể là một cải thiện hiệu suất rất lớn.
Phần trên sẽ trả lời hai trong ba câu hỏi của bạn. Câu hỏi còn lại là: Khi nào bạn xả một luồng? Câu trả lời là: Khi các ký tự nên được ghi vào đích bên ngoài! Điều này có thể xảy ra khi kết thúc việc ghi tệp (mặc dù vậy, việc đóng tệp sẽ xóa bộ đệm một cách ngầm định) hoặc ngay trước khi yêu cầu người dùng nhập vào (lưu ý rằng nó std::cout
sẽ tự động được xóa khi đọc std::cin
như std::cout
là std::istream::tie()
'd đến std::cin
). Mặc dù có thể có một vài trường hợp bạn muốn xả một luồng, tôi thấy chúng khá hiếm.
Cuối cùng, tôi hứa sẽ đưa ra một bức tranh đầy đủ về std::flush
thực tế là: Các luồng là các mẫu lớp có khả năng xử lý các loại ký tự khác nhau (trong thực tế, chúng hoạt động với char
và wchar_t
; khiến chúng hoạt động với các ký tự khác khá liên quan mặc dù có thể làm được nếu bạn thực sự quyết tâm ). Để có thể sử dụng std::flush
với tất cả các mô tả luồng, nó sẽ là một mẫu hàm có chữ ký như sau:
template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);
Khi sử dụng std::flush
ngay lập tức với phần khởi tạo của std::basic_ostream
nó không thực sự quan trọng: Trình biên dịch tự động suy ra các đối số mẫu. Tuy nhiên, trong trường hợp chức năng này không được đề cập cùng với thứ gì đó tạo điều kiện cho việc suy diễn đối số mẫu, trình biên dịch sẽ không suy ra được các đối số mẫu.
Theo mặc định, std::cout
được lưu vào bộ đệm và đầu ra thực tế chỉ được in khi bộ đệm đầy hoặc một số tình huống xả khác xảy ra (ví dụ: một dòng mới trong luồng). Đôi khi bạn muốn đảm bảo rằng quá trình in diễn ra ngay lập tức và bạn cần phải xả bằng tay.
Ví dụ: giả sử bạn muốn báo cáo tiến độ bằng cách in một chấm:
for (;;)
{
perform_expensive_operation();
std::cout << '.';
std::flush(std::cout);
}
Nếu không có xả nước, bạn sẽ không thấy đầu ra trong một thời gian rất dài.
Lưu ý rằng std::endl
chèn một dòng mới vào một dòng cũng như làm cho nó tuôn ra. Vì xả hơi tốn kém, std::endl
không nên sử dụng quá mức nếu việc xả nước không được mong muốn rõ ràng.
cout
không phải là thứ duy nhất được lưu vào bộ đệm trong C ++. ostream
nói chung s thường được đệm theo mặc định, cũng bao gồm fstream
s và những thứ tương tự.
cin
thực hiện đầu ra trước khi nó được xả, không?
Đây là một chương trình ngắn mà bạn có thể viết để quan sát những gì flush đang làm
#include <iostream>
#include <unistd.h>
using namespace std;
int main() {
cout << "Line 1..." << flush;
usleep(500000);
cout << "\nLine 2" << endl;
cout << "Line 3" << endl ;
return 0;
}
Chạy chương trình này: bạn sẽ nhận thấy rằng nó in dòng 1, tạm dừng, sau đó in dòng 2 và 3. Bây giờ, hãy xóa lệnh gọi tuôn ra và chạy lại chương trình- bạn sẽ nhận thấy rằng chương trình tạm dừng và sau đó in cả 3 dòng tại cùng thời gian. Dòng đầu tiên được lưu vào bộ đệm trước khi chương trình tạm dừng, nhưng vì bộ đệm không bao giờ được xóa nên dòng 1 không được xuất cho đến khi có lệnh gọi endl từ dòng 2.
cout << "foo" << flush; std::abort();
. Nếu bạn bình luận ra / loại bỏ << flush
, KHÔNG CÓ ĐẦU RA! PS: các DLL gỡ lỗi tiêu chuẩn mà cuộc gọi abort
là một cơn ác mộng. DLL không bao giờ nên gọi abort
.
Một luồng được kết nối với một cái gì đó. Trong trường hợp đầu ra tiêu chuẩn, nó có thể là bảng điều khiển / màn hình hoặc nó có thể được chuyển hướng đến một đường ống hoặc một tệp. Có rất nhiều mã giữa chương trình của bạn và, ví dụ, đĩa cứng nơi tệp được lưu trữ. Ví dụ: hệ điều hành đang thực hiện công việc với bất kỳ tệp nào hoặc bản thân ổ đĩa có thể đang lưu dữ liệu vào bộ đệm để có thể ghi nó trong các khối có kích thước cố định hoặc chỉ để hiệu quả hơn.
Khi bạn tuôn ra luồng, nó sẽ thông báo cho các thư viện ngôn ngữ, hệ điều hành và phần cứng mà bạn muốn cho bất kỳ ký tự nào mà bạn đã xuất cho đến nay để bị buộc phải lưu trữ. Về mặt lý thuyết, sau một lần 'xả nước', bạn có thể đẩy dây ra khỏi tường và những ký tự đó sẽ vẫn được lưu trữ an toàn.
Tôi nên đề cập rằng những người viết trình điều khiển hệ điều hành hoặc những người thiết kế ổ đĩa có thể tự do sử dụng 'flush' như một gợi ý và họ có thể không thực sự viết các ký tự ra ngoài. Ngay cả khi đầu ra bị đóng, họ có thể đợi một lúc để lưu chúng. (Hãy nhớ rằng hệ điều hành thực hiện tất cả các loại công việc cùng một lúc và có thể hiệu quả hơn nếu đợi một hoặc hai giây để xử lý các byte của bạn.)
Vì vậy, tuôn ra là một loại trạm kiểm soát.
Một ví dụ khác: Nếu đầu ra chuyển đến màn hình bảng điều khiển, một dòng chảy ra sẽ đảm bảo các ký tự thực sự đi đến nơi người dùng có thể nhìn thấy chúng. Đây là điều quan trọng cần làm khi bạn muốn nhập liệu bằng bàn phím. Nếu bạn nghĩ rằng bạn đã viết một câu hỏi vào bảng điều khiển và nó vẫn bị mắc kẹt trong bộ đệm nội bộ nào đó ở đâu đó, người dùng không biết phải nhập câu trả lời nào. Vì vậy, đây là một trường hợp mà việc xả nước là quan trọng.