C++23 `print` 检查写入是否成功进入流吗?

huangapple go评论81阅读模式
英文:

Does C++23 `print` check to see if the write successfully made it into the stream?

问题

我想知道标准委员会是否已经修复了臭名昭著的Hello, world! bug。我主要是在谈论新的<print>库(目前还没有在任何编译器中可用)。

{fmt}库(启发了标准库)并没有修复这个问题。显然,它在输出到/dev/full时不会抛出任何异常(截至版本v9.1.0)。因此,仍然需要使用C I/O函数(比如std::fflush)进行错误处理。

下面的程序注意到错误并返回失败代码(因此不是有bug的):

#include <exception>
#include <cstdio>
#include <cstdlib>
#include <fmt/core.h>

int main()
{
    fmt::println(stdout, "Hello, world!");
    if (std::fflush(stdout) != 0 || std::ferror(stdout) != 0) [[unlikely]]
    {
        return EXIT_FAILURE;
    }
}

但是在C++23中是否可能呢?

#include <print>
#include <exception>
#include <cstdio>
#include <cstdlib>

int main()
{
    try
    {
        std::println(stdout, "Hello, world!");
    }
    catch (const std::exception& ex)
    {
        return EXIT_FAILURE;
    }
}

对于那些不了解"Hello World" bug的人,下面的Rust程序会因为没有设备空间而导致panic,并输出有用的错误消息:

fn main()
{
    println!("Hello, world!");
}
./main > /dev/full 
thread 'main' panicked at 'failed printing to stdout: No space left on device (os error 28)', library/std/src/io/stdio.rs:1008:9

相反,C++标准的iostreams以及其他一些语言(C、Ruby、Java、Node.js、Haskell等)即使在程序关闭文件流时,也不会默认报告任何失败。另一方面,一些其他语言(Python3、Bash、Rust、C#等)在程序关闭文件流时会报告错误。

英文:

I want to know whether or not the standard committee has fixed the infamous Hello, world! bug. I'm primarily talking about the new &lt;print&gt; library (not yet available in any of the compilers).

The {fmt} library (which has inspired the standard library) has not fixed this. Apparently, it does not throw any exceptions when outputting to /dev/full (as of v9.1.0). So the use of C I/O functions like std::fflush for error handling is still a thing.

The below program notices the error and returns a failure code (thus not buggy):

#include &lt;exception&gt;
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;fmt/core.h&gt;


int main()
{
    fmt::println( stdout, &quot;Hello, world!&quot; );
    if ( std::fflush( stdout ) != 0 || std::ferror( stdout ) != 0 ) [[unlikely]]
    {
        return EXIT_FAILURE;
    }
}

But is this possible in C++23?

#include &lt;print&gt;
#include &lt;exception&gt;
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;


int main()
{
    try
    {
        std::println( stdout, &quot;Hello, world!&quot; );
    }
    catch ( const std::exception&amp; ex )
    {
        return EXIT_FAILURE;
    }
}

For those unaware of the "Hello World" bug, the below program (in Rust) panics and outputs a useful error message:

fn main()
{
    println!( &quot;Hello, world!&quot; );
}
./main &gt; /dev/full 
thread &#39;main&#39; panicked at &#39;failed printing to stdout: No space left on device (os error 28)&#39;, library/std/src/io/stdio.rs:1008:9

Conversely, C++ standard iostreams along with some other languages (C, Ruby, Java, Node.js, Haskell, etc) don't report any failure by default even at program shutdown when the program closes the file streams. On the other hand, some others (Python3, Bash, Rust, C#, etc) do report the error.

答案1

得分: 6

以下是您提供的文本的中文翻译:

std::println 函数的文档指出,如果写入流失败(以及其他失败情况),它将抛出 std::system_error 异常。当然,std::println 成功写入流,失败通常发生在实际写入文件系统时。

在 C++ 环境中,如果您需要确保数据实际写入磁盘,那么在某个时刻您将需要使用类似 std::flush 的东西,并检查是否发生了错误。您可以争论这是否方便,但这符合这样的逻辑,即如果您不需要该功能,就不应该有任何开销。这是一项功能,而不是错误。

如果您需要这种保证,可以编写一个小包装器,使用 RAII 技术,在出现错误时抛出异常。这里 有一个关于析构函数中的释放与提交语义以及何时在析构函数中引发异常可能是一个好主意的讨论。

示例代码

#include &lt;iostream&gt;

struct SafeFile {
    SafeFile(const std::string&amp; filename)
        : fp_(fopen(filename.c_str(), &quot;w&quot;))
        , nuncaught_(std::uncaught_exceptions()) {
        if (fp_ == nullptr)
            throw std::runtime_error(&quot;打开文件失败&quot;);
    }

    ~SafeFile() noexcept(false) {
        fflush(fp_);
        if (ferror(fp_) and nuncaught_ == std::uncaught_exceptions()) {
            fclose(fp_);
            throw std::runtime_error(&quot;刷新数据失败&quot;);
        }
        fclose(fp_);
    }

    auto operator*() {
        return fp_;
    }

    FILE *fp_{nullptr};
    int nuncaught_{};
};

int main()
{
    try {
        SafeFile fp(&quot;/dev/urandom&quot;);
        fprintf(*fp, &quot;你好,世界!&quot;);
    }
    catch ( const std::exception&amp; ex )
    {
        std::cout &lt;&lt; &quot;捕获到异常&quot; &lt;&lt; std::endl;
        return EXIT_FAILURE;
    }
}

输出

捕获到异常
英文:

The documentation for the std::println function indicates that it will throw the std::system_error if it fails writing to the stream (and other exceptions for other failures). Of course, std::println successfully writes to the stream, and the failure typically occurs later when the stream is actually written to the filesystem.

In a C++ environment, if you need to guarantee that data actually hits the disk, you will at some point need to use something like std::flush and check that no error has occurred. You can argue whether this is convenient or not, but this follows from the logic that there should not be any overhead if you don't need the feature. This is a feature, not a bug.

If you need this guarantee, write a small wrapper that uses the RAII technique to throw an exception if there is an error. Here is a good discussion about release versus commit semantics in destructors and when it could be a good idea to throw in a destructor.

Sample Code

#include &lt;iostream&gt;

struct SafeFile {
    SafeFile(const std::string&amp; filename)
        : fp_(fopen(filename.c_str(), &quot;w&quot;))
        , nuncaught_(std::uncaught_exceptions()) {
        if (fp_ == nullptr)
            throw std::runtime_error(&quot;Failed to open file&quot;);
    }

    ~SafeFile() noexcept(false) {
        fflush(fp_);
        if (ferror(fp_) and nuncaught_ == std::uncaught_exceptions()) {
            fclose(fp_);
            throw std::runtime_error(&quot;Failed to flush data&quot;);
        }
        fclose(fp_);
    }

    auto operator*() {
        return fp_;
    }

    FILE *fp_{nullptr};
    int nuncaught_{};
};

int main()
{
    try {
        SafeFile fp(&quot;/dev/urandom&quot;);
        fprintf(*fp, &quot;Hello, world!&quot;);
    }
    catch ( const std::exception&amp; ex )
    {
        std::cout &lt;&lt; &quot;Caught the exception&quot; &lt;&lt; std::endl;
        return EXIT_FAILURE;
    }
}

Output

Caught the exception

huangapple
  • 本文由 发表于 2023年5月17日 07:37:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76267723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定