Steel
Steel

Reputation: 51

How to handle a file destructor throwing an exception?

It's important to detect an error when you close a file to which you were writing, because the last part of your data could be flushed during the close, and if it's lost then your last write effectively failed and someone should know about it. The destructor for a file object is a nice place to automatically close it, but people say not to throw exceptions from destructors, so if the close fails then how do you know about it?

I've heard people recommend calling the close() method of the file manually. That's sounds nice, except what happens if the close() methods of multiple files all fail in a situation like this:

    MyFile x(0), y(1), z(2);
    x.close();
    y.close();
    z.close();

?

Well, it seems that if the close() method of 'x' throws an exception then you've done well to uphold the rule to avoid throwing exceptions in the destructor of 'x', except now you're good-intentioned early calls to the close() methods of 'y' and 'z' won't execute until their destructors. So, then when the close() method of 'y' is called in the destructor of 'y' or the close() method of 'z' is called in the destructor of 'z', and if they do throw exceptions then you're screwed.

Is there a reasonable way to not be screwed in such a situation?

Upvotes: 5

Views: 1951

Answers (7)

Loki Astari
Loki Astari

Reputation: 264621

The rule as I see it is:

If you don't care about the exception then let the destructor do the close work (and catch and discard the exceptions). If you do care (AND can do something about it) then close manually and catch the exception and do the appropriate work.

{
    std::fstream   X("Plop_X");
    std::fstream   Y("Plop_Y");
    std::fstream   Z("Plop_Z");

    // Do work

    try
    {
         X.close();
    }
    catch(Plop const& e)
    {
         // Fix the exception
         // Then make sure X closes correctly.

         // If we can't fix the problem
         // rethrow
         if (badJuJu)
         {    throw;
         }
    }
    // Don't care about Y/Z let the destructor close them and discard the exception.
}

Upvotes: 1

Steve Jessop
Steve Jessop

Reputation: 279335

First, MyFile's destructor should catch the exception (and that's an incredibly strong "should" - it's not "must" because the behaviour if it doesn't is well-defined, but it's almost never desirable):

~MyFile() {
    try {
        close();
    } catch(...) {}
    // other cleanup
}

Next, callers should decide whether they want to handle the error. If they don't, then they can just let the destructor be called. If they do, then they have to call close themselves. If your example of three files, supposing that once one of them has failed you don't care about the others, you can do this:

MyFile x(0), y(1), z(2);
try {
    x.close();
    y.close();
    z.close();
} catch(...) {
    std::cerr << "something failed to close\n";
}

If you want to know exactly which failed, you need to ensure that all three close functions are actually called:

MyFile x(0), y(1), z(2);
try {
    x.close();
} catch(...) {
    std::cerr << "x failed to close\n";
}
try {
    y.close();
} catch(...) {
    std::cerr << "y failed to close\n";
}
try {
    z.close();
} catch(...) {
    std::cerr << "z failed to close\n";
}

Of course you might want to common up that code a bit. Also, if you know everything close can throw, then you could have a better catch clause than (...).

This is possibly why the close() function of streams in the standard library doesn't throw an exception unless that behaviour has been enabled by the user setting the exception mask.

Upvotes: 0

erick2red
erick2red

Reputation: 1310

try {
x.close();
y.close();
z.close();
}
catch {
//do what ever you need to do here, then close() what' you'll need to close here
}

That's what i would do ,the point is maybe you don't know which one throw the exceptions, and which one left for closing.

Upvotes: 0

Collin Dauphinee
Collin Dauphinee

Reputation: 13993

In this example, I don't see why anything should be thrown. I don't think that this scenario is exception-worthy at all. In theory, the close didn't fail, it just failed to write the rest of the buffer; which isn't a very exceptional situation. It can be handled and should be handled, unless there's a reason that the file needs to be closed right this instant.

I, personally, would just have my close() function block until the writes have completed, then continue with closing.

Upvotes: 1

dirkgently
dirkgently

Reputation: 111210

This is a FAQ item: 17.3 How can I handle a destructor that fails?

Edit:

Well, it seems that if the close() method of 'x' throws an exception then you've done well to uphold the rule to avoid throwing exceptions in the destructor of 'x', except now you're good-intentioned early calls to the close() methods of 'y' and 'z' won't execute until their destructors.

No. Dtors for y and z will be called when the stack unwinds provided you have a try-catch block installed around the MyFile ... z.close() part. A better idea would be to put a close in the dtor as well. The dtor for x will not be -- so some cleanup is in order in the catch block.

I suggest you run the following program to better understand dtor-calls in the case of an exception (once as-is and again by uncommenting the S x line):

#include <iostream>

using namespace std;

struct s {
 s(int i = 0) : m_i( i ) { cout << __func__ << endl; }
 ~s() { if (m_i == 0) except(); cout << __func__ << endl; }
 void except() { throw 42; }
 int m_i;
};

int main() {
  try
  {
      s y(2), z(3);
      /* s x */
      y.except();
  }
  catch (...) { cout << "exception\n"; }

  cout << "stack unwound\n";
}

Upvotes: 2

Hassan Syed
Hassan Syed

Reputation: 20495

You shouldn't throw from a destructor -- so:

If close had calls that threw exceptions, I would swallow them and do one of the following:

Option 1: Write out an error message and kill the program.

Option 2: Make the error available via a wrapper object (I would probably do this) or a global variable or (prefferably) a variable that is in thread local memory.

Option 1 and 2 both seem reasonable here.

With option 2 and the wrapper you would do:

WrapFileX.close();
WrapFileY.close();
WrapFileZ.close();

if(WrapFileX.hasError || WrapFileY.hasError || WrapfileZ.hasError)
{   //log
    exit(1)
}

Upvotes: 1

JaredPar
JaredPar

Reputation: 755179

Yes, catch the exception thrown from close in the destructor.

It is vitally important that a C++ destructor not throw an exception period. To do otherwise will mess up many of the resource management routines within virtually every available library.

True, you lose the failure information in this case by catching the exception. If the user is actually concerned about the error, they can manually call close and deal with the exception.

Upvotes: 5

Related Questions