std :: flush如何工作?


84

有人可以解释(最好使用简单的英语)如何std::flush工作吗?

  • 它是什么?
  • 您什么时候冲洗河流?
  • 它为什么如此重要?

谢谢。

Answers:


137

由于没有回答到底是 std::flush什么,因此这里有一些细节。std::flush操纵器,即具有特定签名的功能。首先,您可以考虑std::flush使用签名

std::ostream& std::flush(std::ostream&);

但是,实际情况要复杂一些(如果您有兴趣,下面也会对此进行说明)。

流类重载输出运算符采用这种形式的运算符,即,存在一个以操纵器作为参数的成员函数。输出运算符使用对象本身调用操纵器:

std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
    (*manip)(*this);
    return *this;
}

也就是说,当您std::flush与输出“”到时std::ostream,它仅调用相应的函数,即以下两个语句是等效的:

std::cout << std::flush;
std::flush(std::cout);

现在,std::flush()它本身非常简单:它所做的就是调用std::ostream::flush(),即,您可以设想其实现看起来像这样:

std::ostream& std::flush(std::ostream& out) {
    out.flush();
    return out;
}

std::ostream::flush()函数从技术上调用std::streambuf::pubsync()与流相关联的流缓冲区(如果有):当使用的缓冲区溢出或内部表示形式应与字符同步时,流缓冲区负责缓冲字符并将字符发送到外部目标。外部目标,即何时刷新数据。在与外部目标同步的顺序流上,仅意味着立即发送所有缓冲的字符。也就是说,使用std::flush会导致流缓冲区刷新其输出缓冲区。例如,将数据写入控制台时,刷新会导致字符此时出现在控制台上。

这可能会引起问题:为什么不立即写字符?简单的答案是,书写字符通常相当慢。但是,编写合理数量的字符所花费的时间基本上与只写一个字符所花费的时间相同。字符的数量取决于操作系统,文件系统等的许多特征,但是通常最多只能同时写入一个字符,例如最多写入4k个字符。因此,根据外部目标的详细信息,在使用缓冲区发送字符之前对字符进行缓冲可以极大地提高性能。

以上应该回答您三个问题中的两个。剩下的问题是:什么时候冲洗流?答案是:何时将字符写入外部目标!要求用户输入(注意,在此之前可以是在写一个文件的结尾(隐式地关闭文件刷新缓冲区,虽然)或立即std::cout从读出时,自动冲洗std::cinstd::coutstd::istream::tie()“d到std::cin)。尽管在某些情况下您明确想要刷新流,但我发现它们很少见。

最后,我答应给出一个std::flush真正的概况:流是能够处理不同字符类型的类模板(在实践中,它们与char和一起使用wchar_t;使它们与其他字符一起使用相当复杂,尽管如果您确实确定的话是可行的) )。为了能够std::flush与流的所有实例一起使用,它恰好是带有以下签名的函数模板:

template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);

std::flush立即将其实例化std::basic_ostream时并没有关系:编译器自动推导模板参数。但是,如果未同时提及此功能以及可简化模板参数推导的内容,则编译器将无法推断模板参数。


3
很棒的答案。找不到有关冲洗在其他任何地方工作的质量信息。
迈克尔

31

默认情况下,它std::cout是缓冲的,并且仅在缓冲区已满或发生其他刷新情况(例如,流中的换行符)时才打印实际输出。有时,您需要确保立即进行打印,并且需要手动冲洗。

例如,假设您要通过打印单个点来报告进度报告:

for (;;)
{
    perform_expensive_operation();
    std::cout << '.';
    std::flush(std::cout);
}

如果不进行刷新,则很长一段时间都不会看到输出。

请注意,std::endl在流中插入换行符并使其刷新。由于冲洗的价格适中,std::endl因此,如果没有明确要求冲洗,则不应过度使用。


7
只是给读者的补充说明:coutC ++并不是唯一缓冲的东西。ostream通常,默认情况下通常会缓冲fstreams ,其中还包括s等。
Cornstalks 2012

还可以使用usingcin在刷新之前执行输出,不是吗?
扎伊德·汗

21

这是一个简短的程序,您可以编写该程序来观察冲洗功能

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

运行该程序:您会注意到它先打印第1行,然后暂停,然后再打印第2行和第3行。现在删除flush调用并再次运行该程序-您会注意到该程序暂停,然后在命令行上打印所有3行同时。第一行在程序暂停之前就已缓冲,但是由于从未刷新过该缓冲区,因此直到从第2行进行endl调用后才输出第1行。


另一种说明方式是cout << "foo" << flush; std::abort();。如果您评论/删除<< flush,则没有输出!PS:调用的标准输出调试DLLabort是一场噩梦。DLL永不调用abort
Mark Storer

5

流连接到某物。对于标准输出,它可以是控制台/屏幕,也可以重定向到管道或文件。程序与例如文件存储所在的硬盘之间有很多代码。例如,操作系统正在处理任何文件,或者磁盘驱动器本身可能正在缓冲数据以便能够以固定大小的块写入数据,或者只是为了提高效率。

刷新流时,它会告诉语言库,操作系统和硬件,到目前为止您要输出的任何字符都必须被强制存储。从理论上讲,“冲洗”后,您可以将软线从墙壁上踢出,这些字符仍将被安全地存储。

我应该提到的是,编写os驱动程序的人员或设计磁盘驱动器的人员可能会随意使用“ flush”作为建议,而他们可能并没有真正写出字符。即使关闭输出,他们也可能要等待一段时间才能保存它们。(请记住,操作系统同时执行各种操作,而等待一两秒钟来处理您的字节可能会更有效。)

因此,刷新是一种检查点。

再举一个例子:如果输出将显示在控制台显示屏上,则刷新将确保字符实际上完全到达用户可以看到它们的位置。当您期望键盘输入时,这是一件重要的事情。如果您认为已向控制台写了一个问题,但问题仍然停留在某个内部缓冲区中,则用户不知道要输入什么答案。因此,在这种情况下,冲洗很重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.