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:
- RecordGenerator - contains the thread that controls the main flow (get data, format, output)
- FileManager - manages the output files. Records are sent to this class which then puts it in a charging file.
- OutputFile - abstraction of a file that contains records, has
print(), close(), etc. These objects are owned by the
FileManager
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 t开发者_如何学JAVAhan this.
Is there some better way of handling this sort of scenario?
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()
}
精彩评论