std::flush 是如何工作的?

2022-01-07 00:00:00 stream c++

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

Can someone please explain (preferably using plain english) how std::flush works?

  • 这是什么?
  • 你什么时候刷新流?
  • 为什么重要?

谢谢.

推荐答案

既然没有回答 what std::flush 碰巧是,这里有一些细节关于它实际上是什么.std::flush 是一个 manipulator,即具有特定签名的函数.从简单开始,您可以将 std::flush 视为具有签名

Since it wasn't answered what std::flush happens to be, here is some detail on what it actually is. std::flush is a manipulator, i.e., a function with a specific signature. To start off simple, you can think of std::flush of having the signature

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

不过,实际情况要复杂一些(如果您有兴趣,下面也会解释).

The reality is a bit more complex, though (if you are interested, it is explained below as well).

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

The stream class overload output operators taking operators of this form, i.e., there is a member function taking a manipulator as argument. The output operator calls the manipulator with the object itself:

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

也就是说,当你输出"std::flush到一个std::ostream时,它只是调用了对应的函数,即下面两条语句是等价的:

That is, when you "output" std::flush with to an std::ostream, it just calls the corresponding function, i.e., the following two statements are equivalent:

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

现在,std::flush() 本身相当简单:它所做的只是调用std::ostream::flush(),也就是说,你可以设想它的实现看起来像这样:

Now, std::flush() itself is fairly simple: All it does is to call std::ostream::flush(), i.e., you can envision its implementation to look something like this:

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

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

The std::ostream::flush() function technically calls std::streambuf::pubsync() on the stream buffer (if any) which is associated with the stream: The stream buffer is responsible for buffering characters and sending characters to the external destination when the used buffer would overflow or when the internal representation should be synced with the external destination, i.e., when the data is to be flushed. On a sequential stream syncing with the external destination just means that any buffered characters are immediately sent. That is, using std::flush causes the stream buffer to flush its output buffer. For example, when data is written to a console flushing causes the characters to appear at this point on the console.

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

This may raise the question: Why aren't characters immediately written? The simple answer is that writing characters is generally fairly slow. However, the amount of time it takes to write a reasonable amount of characters is essentially identical to writing just one where. The amount of characters depends on many characteristics of the operating system, file systems, etc. but often up to something like 4k characters are written in about the same time as just one character. Thus, buffering characters up before sending them using a buffer depending on the details of the external destination can be a huge performance improvement.

以上内容应该可以回答您的三个问题中的两个.剩下的问题是:你什么时候刷新流?答案是:什么时候应该将字符写入外部目的地!这可能是在写入文件结束时(尽管关闭文件会隐式刷新缓冲区)或在要求用户输入之前立即(请注意,从 std::cout 读取时会自动刷新>std::cin 作为 std::coutstd::istream::tie()'d 到 std::cin).尽管在某些情况下您可能明确希望刷新流,但我发现这种情况很少见.

The above should answer two of your three questions. The remaining question is: When would you flush a stream? The answer is: When the characters should be written to the external destination! This may be at the end of writing a file (closing a file implicitly flushes the buffer, though) or immediately before asking for user input (note that std::cout is automatically flushed when reading from std::cin as std::cout is std::istream::tie()'d to std::cin). Although there may be a few occasions where you explicitly want to flush a stream, I find them to be fairly rare.

最后,我承诺要全面说明 std::flush 的实际含义:流是能够处理不同字符类型的类模板(实际上它们与 charwchar_t;让它们与其他字符一起工作是相当复杂的,尽管如果你真的下定决心是可行的).为了能够在流的所有实例化中使用 std::flush,它恰好是一个具有如下签名的函数模板:

Finally, I promised to give a full picture of what std::flush actually is: The streams are class templates capable of dealing with different character types (in practice they work with char and wchar_t; making them work with another characters is quite involved although doable if you are really determined). To be able to use std::flush with all instantiations of streams, it happens to be a function template with a signature like this:

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

当立即使用 std::flushstd::basic_ostream 实例化时,这并不重要:编译器会自动推导出模板参数.但是,如果此函数未与有助于模板参数推导的内容一起提及,则编译器将无法推导模板参数.

When using std::flush immediately with an instantiation of std::basic_ostream it doesn't really matter: The compiler deduces the template arguments automatically. However, in cases where this function isn't mentioned together with something facilitating the template argument deduction, the compiler will fail to deduce the template arguments.

相关文章