views:

128

answers:

3

Suppose I have a class like this:

#include <iostream>

using namespace std;

class Boda {
    private:
        char *ptr;

    public:
        Boda() {
            ptr = new char [20];
        }
        ~Boda() {
            cout << "calling ~Boda\n";

            delete [] ptr;
        }

        void ouch() {
            throw 99;
        }
};

void bad() {
    Boda b;
    b.ouch();
}

int main() {
    bad();
}

It seems that destructor ~Boda never gets called, thus the ptr resource never get freed.

Here is the output of the program:

terminate called after throwing an instance of 'int'
Aborted

So it seems the answer to my question is No.

But I thought that the stack got unwound when an exception got thrown? Why didn't Boda b object get destructed in my example?

Please help me understand this resource problem. I want to write better programs in the future.

Also, is this the so called RAII?

Thanks, Boda Cydo.

+6  A: 

If the exception is not caught anywhere, then the C++ runtime is free to go straight to terminating the program without doing any stack unwinding or calling any destructors.

However, if you add a try-catch block around the call to bad(), you will see the destructor for the the Boda object being called:

int main() {
    try {
      bad();
    } catch(...) {  // Catch any exception, forcing stack unwinding always
      return -1;
    }
}

RAII means that dynamically (heap) allocated memory is always owned by an automatically (stack) allocated object that deallocates it when the object destructs. This relies on the guarantee that the destructor will be called when the automatically-allocated object goes out of scope, whether due to a normal return or due to an exception.

This corner-case behavior is normally not a problem with respect to RAII, since usually the main reason that you want the destructors to run is to free memory, and all memory is given back to the OS when your program terminates anyway. However if your destructors do something more complicated, like maybe remove a lock file on disk or something, where it would make a difference whether the program called destructors or not when crashing, you may want to wrap your main in a try-catch block that catches everything (only to exit on exception anyway) just to ensure that the stack always unwinds before terminating.

Tyler McHenry
Thanks that explains a lot. But what if I have a more complex situation? Like memory allocated in several places? Then if the destructor gets called, I might end up in a situation where delete (or free) is called on pointers that were not yet actually allocated (because of several places). This leads to having some flags like `bool allocated_in_place_1, allocated_in_place_2, ...`. And in the destructor I have to go like `if (allocated_in_place_1) delete place_1_ptr; if (allocated_in_place_2) delete [] place_2_array_ptr;` Do you have any tips for this?
bodacydo
I can't just go `delete place_1_ptr; delete [] place_2_array_ptr` in the dtor, right?
bodacydo
Yes, if you have an object that is going to be managing some complex combination of dynamic memory it does need to keep track of what memory it has actually allocated and what it has not, so that it can delete the correct things in its destructor. But that's pretty easy to do if you just make sure pointer that haven't had memory allocated to them are always set to `NULL`. And it's not common that you'd write such a class yourself anyway. Normally you'd want to use an STL container or smart pointers.
Tyler McHenry
Ow, yes. I forgot that if they are `NULL` then it's ok.
bodacydo
You can just write `delete place_1_ptr; delete [] place_2_array_ptr` if you are sure that both pointers are either allocated or NULL. Deleting a NULL pointer is legal and nothing happens when you do it.
Tyler McHenry
Wow, this is what makes Stack overflow so great, thanks @Tyler.
David Gladfelter
But again, what if I have another complex situation, where I have called `init_library_x`, `init_library_y` C-isms?Then I would have to keep flags `lib_x_inited` and `lib_y_inited`, wouldn't I?Seems to be a pattern when working with C code libraries, so can you comment a little on your "it's not common that you'd write such a class yourself"?
bodacydo
@David Actually, I think I learned this myself from Neil Butterworth or one of the other high-rep C++ answerers on SO a while back, but I have no idea what question it was on. So yeah, SO is awesome. :)
Tyler McHenry
David Gladfelter, yes. I love this place so much. I would be so much worse programmer if people weren't that friendly and helpful! Thank you Tyler and Nikolai and everyone else! :)
bodacydo
@bodacydo Yeah, if you're stuck working with legacy C code there's not much you can do except lots of explicit bookkeeping. But that's what C code does, so when in C, do as the C coders do. But if you're writing from the ground up in C++, you'd rarely need to allocate lots of different blocks of memory yourself; in most situations, using `std::vector`, `std::map`, etc, suits the task just fine, and they take care of the RAII stuff for you.
Tyler McHenry
Perfect, I have now become a tiny bit better programmer. Thanks :)
bodacydo
@bodyacydo: if you're working with a C library, it's easy enough to write a simple wrapper which will create resources in the ctor and destroy them in the dtor. One tricky case you have to consider is throwing from a ctor. That doesn't invoke a dtor and if you allocate multiple resources in a ctor, you generally need a try/catch block and avoid an initializer list (unless you're using smart pointers already like scoped_ptr).
+1  A: 

Try flushing the stream - you will see that the destructor is indeed called:

cout << "calling ~Boda" << endl;

It's the buffering of the I/O that delays the printout to the point that program termination cuts in before actual output.

Edit:

The above holds for handled exceptions. With unhandled exceptions the standard does not specify whether stack is unwound or not. See also this SO question.

Nikolai N Fetissov
The embedded `\n` will flush the stream, assuming `cout` is pointing at a terminal.
Tyler McHenry
I added `endl` but it didn't. hmm...
bodacydo
@Tyler : can you point to somewhere that states that embedded `\n` will flush the terminal? I've found that not to be the case, but I'm not sure if I've only noticed in files.
Stephen
Hmm, who told you that? Read this please: http://www.cplusplus.com/doc/tutorial/basic_io/
Nikolai N Fetissov
Oops, sorry, upon further reading I think that flush-on-`\n` is a gcc-ism, brought over from C99.
Tyler McHenry
+1  A: 

The destructor won't be run if an exception occurs in the constructor.

It will be run if necessary (if exception is handled somewhere) if exception is raised in another method like in your example. But as the program is terminated, calling the destructor is not necessary here and behavior depends of compiler...

The idea of RAII is that constructor allocates ressources and destructor frees them. If an exception occurs in constructor, there is no simple way to know wich ressources where allocated and which were not (it depends on the exact place in the constructor where exception occured). You should also remember that if a constructor fails, the only way to say it to caller it to raise an exception and allocated memory is freed (either stack unwinding, or heap allocated memory) as if it were never allocated.

The solution is obvious : if any exception may occur inside a constructor, you have to catch it and free allocated ressources if necessary. It may actually be some duplicated code with destructor, but that's not a big problem.

In destructor you should not raise exceptions, as it can lead to big troubles with stack unwinding.

In any other method, use exceptions as you like, but do not forget to handle them somewhere. An unhandled excception may be worse than no exception at all. I know of some programs that does not handle exceptions for some minor errors... and crash for errors that should only issue a warning.

kriss