In a typical example of RAII for File I/O on Wikipedia, any errors that occur when closing the file are swallowed:
#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>
void write_to_file (const std::string & message) {
// try to open file
std::ofstream file("example.txt");
if (!file.is_open())
throw std::runtime_error("unable to open file");
// write message to file
file << message << std::endl;
// file will be closed when leaving scope (regardless of exception)
}
It seems there is no way to determine if an error occurred when file
is automatically closed; obviously one can only call file.rdstate()
while file
is on scope.
I could call file.close()
manually and then check for an error, but I would have to do that at every place I return from the scope, which defeats the purpose of RAII.
Some have commented that only unrecoverable errors like file system corruption can occur within the destructor, but I don't believe that's true because AFAIK the destructor flushes the file before closing it, and recoverable errors could occur while flushing.
So is there a common RAII way to get errors that occur during destruction? I read that throwing exceptions from destructors is dangerous so that doesn't sound like the right approach.
The simplest way I can think of is to register a callback function that the destructor will call if any errors occur during destruction. Surprisingly it doesn't seem like there is an event for this supported by ios_base::register_callback
. That seems like a major oversight, unless I misunderstand something.
But perhaps a callback is the most common way to get notified of errors during destruction in modern class designs?
I assume calling an arbitrary function in a destructor is also dangerous, but perhaps wrapping the call in a try/catch
block is completely safe.
You might partially handle the case of failure in destructor:
class Foo {
public:
Foo() : count(std::uncaught_exceptions()) {}
~Foo() noexcept(false)
{
if (std::uncaught_exceptions() != count) {
// ~Foo() called during stack unwinding
// Cannot throw exception safely.
} else {
// ~Foo() called normally
// Can throw exception
}
}
private:
int count;
};
If you have specific code handling any errors from the file closure when you unwind, you could add another level of abstraction...
class MyFileHandler {
std::ofstream& f_;
public:
MyFileHandler(std::ofstream& f) : f_(f) {}
~MyFileHandler() {
f_.close();
// handle errors as required...
}
// copy and assignments elided for brevity, but should well be deleted
};
void write_to_file (const std::string & message) {
// try to open file
std::ofstream file("example.txt");
MyFileHandler fileCloser(file);
if (!file.is_open())
throw std::runtime_error("unable to open file");
// write message to file
file << message << std::endl;
// file will be closed when leaving scope (regardless of exception)
}
Depending on your use case, you could embed the std::ofstream
in the class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With