Search code examples
c++error-handlingdestructorraiistack-unwinding

RAII way to get errors that are caught during destruction


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.


Solution

  • 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;
    };