C++ Throwing exceptions from destructors

evilrixSenior Software Engineer (Avast)
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.
Published:
Updated:
You're implementing a beautiful class. It's just wonderful - the most perfect code you've ever written, except... for some reason it keeps crashing and you don't know why. You've debugged the code and discovered it happens when your class goes out of scope and needs to throw an exception from the destructor. Tested in isolation, this works fine but integrated into the main code base it causes a crash. You know there are exception handlers that should deal with this and yet it still crashes. This doesn't make sense. Why is the crashing?

As part of the development of this class it was necessary to write some complex "clean-up" code in the class destructor, but there is the possibility that something could fail during the clean up. For example, maybe our use case for the class is an object that represents a database connection and the destructor is finalising any outstanding transactional data commits before the object goes out of scope.

Your class can't just leave these commits unfinalised as that could leave the database in an inconsistent state, but the process can fail. What do you do? You have to handle these commits but they can go wrong and so you have no choice but to throw an exception of course, right? Wrong! This really is about the most dangerous thing you could possibly do! Also, if your class is written correctly it should be completely unnecessary.

Here's a question for you. Let's say the destructor of your class is being executed because the stack is unwinding due to another exception that was already thrown. The destructor of your class encounters its own error and so throws its own exception. Question: what happens next?

If your answer was that the new exception gets thrown in place of the original please go to the back of the class, stand in the corner and face the wall. You're wrong. Very wrong. Badly, dangerously, seriously wrong! The C++ Standard document is very clear on what happens next: your program is terminated, abruptly! Yes, you read that correctly. This is not a mis-print or even me messing with you. Your program is just terminated. Good bye dear. 

Don't believe me? Try it!
 
#include 
                      #include 
                      
                      class StupidClass
                      {
                         public:
                            ~StupidClass()
                            {
                               // this is just asking for trouble!
                               throw std::runtime_error("this is stupid");
                            }
                      };
                      
                      int main()
                      {
                         try
                         {
                            auto && sc = StupidClass();
                            throw std::runtime_error("something wicked this way comes");
                         }
                         catch(...) // catch all just used for demonstration purposes!
                         {
                            // I can't help you - code never gets here!
                            std::cerr << "handled error!" << std::endl;
                         }
                      }
                      

Open in new window


Now, to be clear (in case I haven't been so far), termination is abrupt. It is not a nice and friendly request to exit, it is an abrupt and immediate termination. No further stack unwinding takes place, no more destructors are called and there is no opportunity to purge data and close files. Your program is given no chance to perform any further clean up.

In the C++-3 standard, this only happens if your destructor emits an exception during a current stack-unwinding but in the new C++11 standard it's even more specific: even if the stack isn't currently unwinding, due to another exception, the result is still immediate termination of your program. This is now the default behaviour on all class destructor in C++11, they terminate if an exception is allowed to emanate from the destructor.

So, the very blunt point of this article: DO NOT let exceptions emit from the destructors of C++ classes. Ever!!!

Okay, so what do we do if our destructor needs to handle a failure? Easy, you don't put such code into the destructor.
Instead, you should put such clean-up code in a separate function that can be called before the class goes out of scope. In the case of our example database connection you might add a "flush" method that the user is expected to call before the class goes out of scope.

Ensuring you've added this function provides the user the option of finalising usage of the class before the destructor is called. This avoids the situation of the destructor needing to do the work that may fail. Given our example use case, you've now given the user the ability to ensure the object is flushed before the class goes out of scope. This affords them the opportunity to deal with any errors that may happen during the clean-up process; the user can catch and deal with them.

You expect the user to have already called this clean-up function by the time the class is destructed; however, for the sake of ensuring your class is a good citizen (who always clean up after themselves) the destructor should still call the clean-up method if it wasn't called by the user, but it MUST swallow any exceptions that are emitted if the clean-up fails. Unfortunately, this means the user of your class has no idea of the failure, but that's their fault for not following the correct usage instructions for your class!

My advice would be to add a debug assert to your class such that in a debug build; if the clean-up is not performed by the time the destructor is called it should fire an assert. At least in this way the developer will know s/he has used your class wrongly.

Time for an example:
 
#include <cassert>
                      #include <stdexcept>
                      #include <iostream>
                      
                      class StupidClass
                      {
                         public:
                      
                            void CleanUp()
                            {
                               // I'm a stupid function that always throws - duh!
                               dirty_ = false;
                               throw std::runtime_error("this is stupid");
                            }
                      
                            ~StupidClass()
                            {
                               // if this triggers class is being used wrong!
                               assert("your class is still dirty" && !dirty_);
                      
                               try
                               {
                                  // just in case the user of this class was dumb and didn't read
                                  // the instructions on how to safely and correctly destroy me.
                                  if(dirty_)
                                  {
                                     // if it fails all we can do is ignore (maybe log to log file)
                                     CleanUp();
                                  }
                               }
                               catch(...)
                               {
                                  // Eeek, dragons! Sadly, we've had to ignore them.
                                  assert("clean-up failed" && false);
                               }
                            }
                      
                         public:
                            bool dirty_; // if set the class needs cleaning
                      };
                      
                      int main()
                      {
                         try
                         {
                            auto && sc = StupidClass();
                            sc.CleanUp(); // errors but at least destructor won't terminate now
                         }
                         catch(...) // catch all just used for demonstration purposes!
                         {
                            std::cerr << "handled error!" << std::endl;
                         }
                      }
                      

Open in new window


As you can see, there is a specific function provided to allow clean-up and the user of the class is expected to call it before the class is destructed. As a fail-safe the destructor will call the clean-up method if the class is still dirty; however, this is a last resort and if there are any errors they'll slip by silently (unless logged). In a debug build an "assert" will trigger if the class is still dirty upon destruct and also if the clean-up emits an error. In a release build this will be silently ignored.
 
1
2,553 Views
evilrixSenior Software Engineer (Avast)
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.