Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 625
Andy Avatar asked Feb 20 '18 08:02

Andy


2 Answers

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;
};
like image 147
Jarod42 Avatar answered Oct 23 '22 11:10

Jarod42


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.

like image 31
Niall Avatar answered Oct 23 '22 11:10

Niall