英文:
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 <print>
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 <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;
}
}
But is this possible in 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;
}
}
For those unaware of the "Hello World" bug, the below program (in Rust) panics and outputs a useful error message:
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
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 <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("打开文件失败");
}
~SafeFile() noexcept(false) {
fflush(fp_);
if (ferror(fp_) and nuncaught_ == std::uncaught_exceptions()) {
fclose(fp_);
throw std::runtime_error("刷新数据失败");
}
fclose(fp_);
}
auto operator*() {
return fp_;
}
FILE *fp_{nullptr};
int nuncaught_{};
};
int main()
{
try {
SafeFile fp("/dev/urandom");
fprintf(*fp, "你好,世界!");
}
catch ( const std::exception& ex )
{
std::cout << "捕获到异常" << 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 <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;
}
}
Output
Caught the exception
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论