7

Let's have a piece of code (fstream is just an example, we could be talking about dynamic memory allocation...):


fstream f;
try {
f.open("xxx");
    ...
f.close();
} catch (...) {
    ...
}

When something goes wrong I would like to close() the file (release memory or whatever), but I don't know the state of f. After all, the exception may come from f.open(). I don't think it would be safe to call f.close() in the catch clause as I can no longer believe f.

f could also be a pointer to a dynamically allocated array which I would like to delete [], but who knows where it points to after the exception was thrown...

This may not be very common, but what can I do when I absolutely can't affort any additional damage?

I can think about an immediate abort().

Thanks.

Petr
  • 1,128
  • 2
  • 14
  • 23

6 Answers6

11

You should use RAII or popularly known here as SBRM (Scope Based Resource Management) :)

Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • +1 as RAII (SBRM) can be used for more than just fstream (the other responses seem to focus on the fstream aspect). If you wrap, for example, memory allocation in smart pointers then RAII should work here too. – icabod May 13 '11 at 11:21
10

fstream destructors call close for you. When an exception is thrown, the file is closed automatically.

For managing memory, you can use smart pointers.

For managing mutexes or more general locks, most libraries provide you with a class whose destructor unlocks the mutex for you.

Never write code in the form:

acquire a resource
do stuff which can throw
release a resource

Instead, use objects whose destructors release the resource for you.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 1
    @Alexandre C. OK, I am getting this, but technically, can I still trust the application (stack) after an exception was thrown? – Petr May 13 '11 at 11:27
  • @Petr: An exception automatically unwinds the stack, that is, destroys any local object and calls those objects destructors. So if normal execution or an exception happens, everything will be freed / closed / released / whatever. :) – Xeo May 13 '11 at 11:33
  • @Xeo Yes, but what if portions of the stack were somehow overwritten and that caused the exception. Do I still want to unwind the stack? – Petr May 13 '11 at 11:36
  • 3
    If something unbalances the stack, like a buffer overflow, then you're generally screwed. Just don't let anything like that happen. ;) – Xeo May 13 '11 at 11:38
  • @Xeo So, when I know this could be a problem I need to abort() immediatelly? – Petr May 13 '11 at 11:41
  • 4
    @Petr: When something like that becomes a problem, it's usually too late to do anything at all (except to debug). There is no way for you to detect those problems from within your code. – molbdnilo May 13 '11 at 12:00
  • 1
    @Petr: what you are talking about is called "Undefined Behavior" by the standard. When Undefined Behavior is caused, then by definition the behavior is undefined, and no guarantee can be made. Note that an exception is fundamentally different from Undefined Behavior because it is well-specified and managed. – Matthieu M. May 13 '11 at 14:47
4

The fstream destructor will call close() for you, so you don't really need to close it yourself (unless you want to see the return code of close()).

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    Explicit close can throw if the last write, i.e. buffer flush failed. The automatic close in the destructor will never close. If you want to know that your file write succeeded right until the end, you should always use explicit close. – CashCow May 13 '11 at 11:29
  • @Cashcow - I guess you mean "...will never throw."? If the close is to free resources, the destructor will do its best at that. If you want to know the result, you will have to call close yourself. – Bo Persson May 13 '11 at 11:47
  • @CashCow: fixed: "... will never throw (strike: close)" – sehe May 13 '11 at 11:47
2

In your example, you can move the declaration of f into the try block to ensure that it destroys itself; the destructor knows the object's state.

As another example, with memory allocation you can initialize the pointer to 0 before memory is actually allocated and then again reset it to zero when you release allocated memory. This let's you check if memory was allocated to avoid releasing memory that is no longer yours. Example:

char *name = 0;
try {
    //  perform some operations which may throw...

    //  now allocate
    name = new char[20];

    //  more error prone operations here
}
catch(...){
    if(name != 0){
        delete[] name;
        name = 0;
    }
}

Again, you could use RAII here as well. Example:

class MyCharArray {
    char *str;
public:
    MyCharArray(size_t size){
        str = new char[size];
    }

    ~MyCharArray(){
       delete[] str;
   }
};

int main(){
    try {
        //  perform some operations which may throw...

        MyCharArray name(20);

        //  more error prone operations here
    }
    catch(...){
        //  no additional error handling required here
    }
    return 0;
}

Note that RAII is considered superior because, you write clean-up code only once -- in the destructor -- and not after every try block.

Agnel Kurian
  • 57,975
  • 43
  • 146
  • 217
  • You don't need to check the pointer before deleting - it's valid and safe to delete the null pointer. – molbdnilo May 13 '11 at 11:54
  • @molbdnilo: corrected. i still don't need the if... more of a habit i guess. the important part is to set the pointer to 0 and prevent further delete[] calls. – Agnel Kurian May 13 '11 at 11:58
1

Enable exceptions on the fstream object, and handle the exception at the point where it is possible :

void foo()
{
  std::fstream f( "lala.txt" );
  f.exceptions( std::fstream::failbit | std::fstream::badbit )
  // do something
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273
0

I wouldn't consider using boost to do the RAII to be cheating:

#include <boost/smart_ptr.hpp>
#include <fstream>

using namespace std;

static void do_close(fstream* f)
{
    try 
    { 
        f->close(); 
        delete f;
    } catch(...) { /* log the error? */ }
}

int main()
{
    boost::shared_ptr<fstream> f(new fstream(), &do_close);
    try 
    {
        f->open("xxx");
        f->close();
    } catch (...)
    {
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633