Does destructor of a C++ class that throws an exception gets called?
Asked Answered
B

3

6

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.

Blaubok answered 27/6, 2010 at 23:51 Comment(0)
F
9

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.

Foreordain answered 27/6, 2010 at 23:53 Comment(12)
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?Blaubok
I can't just go delete place_1_ptr; delete [] place_2_array_ptr in the dtor, right?Blaubok
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.Foreordain
Ow, yes. I forgot that if they are NULL then it's ok.Blaubok
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.Foreordain
Wow, this is what makes Stack overflow so great, thanks @Tyler.Ballflower
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"?Blaubok
@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. :)Foreordain
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! :)Blaubok
@Blaubok 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.Foreordain
Perfect, I have now become a tiny bit better programmer. Thanks :)Blaubok
@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).Racehorse
P
2

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.

Polyunsaturated answered 28/6, 2010 at 0:29 Comment(0)
T
1

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.

Tongue answered 27/6, 2010 at 23:55 Comment(4)
The embedded \n will flush the stream, assuming cout is pointing at a terminal.Foreordain
@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.Chavey
Hmm, who told you that? Read this please: cplusplus.com/doc/tutorial/basic_ioTongue
Oops, sorry, upon further reading I think that flush-on-\n is a gcc-ism, brought over from C99.Foreordain

© 2022 - 2024 — McMap. All rights reserved.