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

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

提问人:digito_evo 提问时间:5/17/2023 最后编辑:user229044digito_evo 更新时间:5/19/2023 访问量:433

问:

我想知道标准委员会是否已经修复了臭名昭著的 Hello, world! 错误。我主要谈论的是新的<print>库(尚未在任何编译器中提供)。

{fmt} 库(启发了标准库)尚未修复此问题。显然,它在输出到(从 v9.1.0 开始)时不会抛出任何异常。因此,使用 C I/O 函数(例如用于错误处理)仍然是一回事。/dev/fullstd::fflush

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

#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”错误的人,下面的程序(在 Rust 中)会崩溃并输出一条有用的错误消息:

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++标准以及其他一些语言(C、Ruby、Java、Node.js、Haskell 等)默认情况下不会报告任何故障,即使在程序关闭时,程序关闭文件流时也是如此。另一方面,其他一些(Python3、Bash、Rust、C# 等)确实报告了错误。iostreams

C++ 错误处理 IO FMT C++23

评论

0赞 Samuel Liew 5/18/2023
评论已移至聊天室;请不要在此处继续讨论。在下方发表评论之前,请查看评论的目的。不要求澄清或提出改进建议的评论通常属于 Meta Stack Overflow 或 Stack Overflow Chat 中的答案。继续讨论的评论可能会被删除。

答:

6赞 RandomBits 5/17/2023 #1

std::p rintln 函数的文档表明,如果它无法写入流,它将抛出 (以及其他失败的其他异常)。当然,成功写入流,失败通常发生在流实际写入文件系统时。std::system_errorstd::println

在 C++ 环境中,如果您需要保证数据确实命中磁盘,您将在某些时候需要使用类似的东西并检查是否没有发生错误。您可以争论这是否方便,但这是从逻辑得出的,如果您不需要该功能,则不应有任何开销。这是一个功能,而不是一个错误。std::flush

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

示例代码

#include <iostream>

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

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

    auto operator*() {
        return fp_;
    }

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

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

输出

Caught the exception

评论

2赞 RandomBits 5/17/2023
您可能不同意结果,但正如其他人所说,没有问题需要解决。这只是语言/库工作方式的一个功能。
3赞 spectras 5/17/2023
@digito_evo那里没有问题。如果要验证刷新是否成功,则必须验证刷新是否成功。
3赞 Nicol Bolas 5/17/2023
@digito_evo:是的。这就是所谓的“不要为你不使用的东西付费”。您可以对流执行多个输出,然后在最后刷新它们。这样,您只需支付一次冲洗费用。如果它总是冲洗,你就不能这样做。
3赞 Nicol Bolas 5/18/2023
@digito_evo:“std 库需要对文件流负责,如果在程序关闭时无法刷新它们,则返回失败代码。这样做的成本不为零。它要求每个人都做一些只有某些语句需要做的事情。 C++ 是一种语言,通常希望你对非零成本的事情负责。就是这样。这不是一个错误;这是一个功能。printprint
3赞 Nicol Bolas 5/18/2023
@digito_evo:“这是要付出代价的。但不是在每份印刷声明中这就是重点。“修复”的唯一方法是让它在每次打印报表时支付该成本。这违反了“不要为不使用的东西付费”的规则。API 不知道是否有任何特定语句需要执行刷新。因此,执行此类刷新是您不知道用户是否想要或需要的成本。所以你让他们在他们想做的时候做。print