MahlerFive
MahlerFive

Reputation: 5289

How to handle object destruction in error case vs. non-error case

I have a program that is responsible for reading data, formatting it and creating records, and outputting records to files. The important classes for this discussion are:

During a normal process shutdown, the destructors for these classes all get called which causes all remaining records to get flushed out to the current output file and then it gets closed. This ensures we don't lose any data. However, during an error case, we need to shutdown but we don't want to flush and close the file since the data is likely corrupt. Normally what happens is an exception will get thrown which gets caught in the RecordGenerator which then decides if this is a fatal error or not. If it is, it will initiate the application shutdown. It's at this point that the FileManager gets destructed, but needs to know whether there is an error. Likewise, when the FileManager gets destructed, this causes the OutputFile to get destructed which also needs to know whether there is an error.

My first reaction was to just add some public functions that set error flags for these classes, so RecordGenerator could call FileManager::setErrorFlag() which can then call OutputFile::setErrorFlag(). Adding a chain of these seems like a pretty bad smell to me, especially if you consider the object chain could be much longer than this.

Is there some better way of handling this sort of scenario?

Upvotes: 0

Views: 137

Answers (1)

Yakov Galka
Yakov Galka

Reputation: 72539

This is a typical problem when people start using RAII the way it's not meant to be used. Destructors should clean resources and revert whatever they are responsible to. They should not commit changes. Typical exception safe C++ code looks like this:

  • allocate resource
  • do something
  • commit changes

For example:

X& X::operator = (const X& x)
{
    X y(x); // allocate
    this->swap(y); // commit
    return *this;
}

void f()
{
    Transaction t(...); // begin transaction
    // operate
    t.commit(); // commit transaction
}

void g()
{
    File f(...); // open file
    // write to file
    f.flush(); // flush the buffers, this may throw but not f.~File()
}

Upvotes: 1

Related Questions