273

Most people say never throw an exception out of a destructor - doing so results in undefined behavior. Stroustrup makes the point that "the vector destructor explicitly invokes the destructor for every element. This implies that if an element destructor throws, the vector destruction fails... There is really no good way to protect against exceptions thrown from destructors, so the library makes no guarantees if an element destructor throws" (from Appendix E3.2).

This article seems to say otherwise - that throwing destructors are more or less okay.

So my question is this - if throwing from a destructor results in undefined behavior, how do you handle errors that occur during a destructor?

If an error occurs during a cleanup operation, do you just ignore it? If it is an error that can potentially be handled up the stack but not right in the destructor, doesn't it make sense to throw an exception out of the destructor?

Obviously these kinds of errors are rare, but possible.

Martin York
  • 234,851
  • 74
  • 306
  • 532
Greg Rogers
  • 33,366
  • 15
  • 63
  • 93
  • 38
    "Two exceptions at once" is a stock answer but it's not the REAL reason. The real reason is that an exception should be thrown if and only if a function's postconditions cannot be met. The postcondition of a destructor is that the object no longer exists. ***This can't not happen.*** Any failure-prone end-of-life operation must therefore be called as a separate method before the object goes out of scope (sensible functions normally only have one success path anyway). – spraff Aug 30 '11 at 10:13
  • 1
    @spraff So any function with an empty post-condition could catch and discard all exceptions? – curiousguy Sep 29 '11 at 23:29
  • 3
    It should catch *and handle* all exceptions (discarding may be acceptable) OR it should rewrite its post-condition to say "if the input does not satisfy X, the output is Y/undefined" – spraff Oct 04 '11 at 08:07
  • 2
    If it's possible for the destructor to fail, your design is broken: anything which is necessary for successful destruction should be established by the constructor. – spraff Oct 04 '11 at 08:11
  • 34
    @spraff: Are you aware that what you said implies "throw away RAII"? – Kos Oct 26 '11 at 13:30
  • 2
    Quite the opposite, actually. – spraff Oct 27 '11 at 07:52
  • 17
    @spraff: having to call "a separate method before the object goes out of scope" (as you wrote) actually throws away RAII! Code using such objects will have to ensure that such a method will be called before the destructor is called.. Finally, this idea does not help at all. – Frunsi Aug 08 '12 at 02:00
  • 10
    @Frunsi no, because this problem stems from the fact that the destructor is trying to do something beyond the mere releasing of resources. It's tempting to say "i always want to end up doing XYZ" and thinking this is an argument for putting such logic in the destructor. No, don't be lazy, write `xyz()` and keep the destructor clean of non-RAII logic. – spraff Aug 11 '12 at 21:11
  • 3
    > If an error occurs during a cleanup operation, do you just ignore it? This is the true question. Mentioning "destructor" only cause useless automatic answers like "destructor shall not throw period". The only possible answer to such general questions is "it depends". – curiousguy Sep 30 '11 at 05:23
  • 6
    @Frunsi For example, committing something to file _isn't_ necessarily OK to do in the destructor of a class representing a transaction. If the commit failed, it's too late to handle it when all the code that was involved in the transaction has gone out of scope. The destructor should discard the transaction unless a `commit()` method is called. – Nicholas Wilson Aug 21 '13 at 09:44
  • This article is a more recent followup discussion on this problem http://cpp-next.com/archive/2012/08/evil-or-just-misunderstood/ – ThomasMcLeod Dec 24 '13 at 13:55
  • You can throw an exception in a destructor, but that exception must not leave the destructor; if a destructor exits by a throw, all kinds of bad things are likely to happen because the basic rules of the standard library and the language itself will be violated. Don't do it. – Jnana Nov 15 '15 at 11:06
  • @Jnana This "argument" is patently absurd. Not every object ends up in a container. – curiousguy Jan 30 '17 at 02:00
  • @spraff "_No, don't be lazy, write xyz() and keep the destructor clean of non-RAII logic_" in the `catch` block? – curiousguy Jan 30 '17 at 02:02

16 Answers16

206

Throwing an exception out of a destructor is dangerous.
If another exception is already propagating the application will terminate.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

This basically boils down to:

Anything dangerous (i.e. that could throw an exception) should be done via public methods (not necessarily directly). The user of your class can then potentially handle these situations by using the public methods and catching any potential exceptions.

The destructor will then finish off the object by calling these methods (if the user did not do so explicitly), but any exceptions throw are caught and dropped (after attempting to fix the problem).

So in effect you pass the responsibility onto the user. If the user is in a position to correct exceptions they will manually call the appropriate functions and processes any errors. If the user of the object is not worried (as the object will be destroyed) then the destructor is left to take care of business.

An example:

std::fstream

The close() method can potentially throw an exception. The destructor calls close() if the file has been opened but makes sure that any exceptions do not propagate out of the destructor.

So if the user of a file object wants to do special handling for problems associated to closing the file they will manually call close() and handle any exceptions. If on the other hand they do not care then the destructor will be left to handle the situation.

Scott Myers has an excellent article about the subject in his book "Effective C++"

Edit:

Apparently also in "More Effective C++"
Item 11: Prevent exceptions from leaving destructors

Community
  • 1
  • 1
Martin York
  • 234,851
  • 74
  • 306
  • 532
  • 7
    "Unless you don't mind potentially terminating the application you should probably swallow the error." - this should probably be the exception (pardon the pun) rather than the rule - that is, to fail fast. – Erik Forbes Sep 24 '08 at 22:05
  • 16
    I disagree. Terminating the program stops the stack unwind. No more destructor will be called. Any resources opened will be left open. I think swallowing the exception would be the prefered option. – Martin York Sep 24 '08 at 22:15
  • When the application goes down, then it's up to the OS to handle cleaning up any leftover resources. – Eclipse Oct 03 '08 at 17:55
  • 22
    The OS acan clean up resources it is the owner off. Memory, FileHandles etc. What about complex resources: DB connections. That uplink to the ISS you opened (is it automatically going to send the close connections)? I am sure NASA would want you to close the connection cleanly! – Martin York Oct 03 '08 at 18:02
  • 7
    If an application is going to "fail fast" by aborting, it shouldn't be throwing exceptions in the first place. If it is going to fail by passing control back up the stack, it should not do so in a way that may cause the program to be aborted. One or the other, don't pick both. – Tom Dec 13 '08 at 22:06
  • @Martin: If the application is closing DB connections in the destructor, it may not be properly designed. –  Jun 06 '11 at 18:51
  • 2
    @Eclipse "_it's up to the OS to handle cleaning up any leftover resources_" some resources are shared between processes: Sys V IPC; POSIX mutex/sema... can be shared too. Or just temp files. There are many resources the OS cannot possible clean-up safely. You might want to have a watcher process that detects termination of your process and deals with this. – curiousguy Sep 29 '11 at 23:30
  • @dog44wgm: the original poster referenced the book "Effective C++" by Scott Myers. "More Effective C++" is another book, and although it may talk about the same subject, the wording may be completely different. – Adrien Plisson Dec 16 '11 at 15:17
  • 4
    @LokiAstari The transport protocol you are using to communicate with a spacecraft can't handle a dropped connection? Ok... – doug65536 Feb 09 '14 at 15:41
  • @doug65536: Is that an assumption you want to make (with you $100 Billion ship on its way to Mars)? – Martin York Feb 09 '14 at 17:33
  • @Deduplicator: What does this add to the current discussion. – Martin York Oct 13 '14 at 00:52
  • @LokiAstari: Just that, if I'm reading the C++11 and C++14 standards correctly, the dtor will have a default `noexcept(true)`-specification, which would mean it must not throw an exception under any circumstances. Correct me if I'm wrong. – Deduplicator Oct 13 '14 at 00:58
  • 1
    I work on embedded systems. I don't ever want to see a destructor throw an exception... the whole bit about "it's up to the OS to handle cleaning up any leftover resources" CANNOT be relied on in this context. I'd be leaving all kinds of stuff in bad states. – Erin Aug 10 '15 at 20:51
  • @mangguo: Then you are in an even worse state with C++11. The default is that destructor is marked as `noexcept` which means if a program throws and exception out of the destructor then application terminates with no stack unwinding and no other destructors being called (you can not even expect the destructors between the throw point and the destructor to be called either). – Martin York Aug 10 '15 at 22:22
  • 1
    But in reality don't you turn off exceptions when doing embedded work (its been two decades since I did any and we did not use exceptions for embeded stuff then). If not then you must explicitly add a try/catch block to all your destructors and discard all exceptions. – Martin York Aug 10 '15 at 22:22
  • @LokiAstari I thought terminate being called was a given ;) Also, we're in the process of refactoring our exception usage... the problem I'm running into right now is a former coworker brilliantly put something that could throw an exception into a destructor without a try/catch. So yes, I agree with you. By some black magic we didn't run into issues in the past. – Erin Aug 10 '15 at 22:42
  • 2
    @mangguo: What would be even nicer is way to declare a destructor that discards exceptions. `~myClass() noexcept(true | false | discard)` :-) Note: In C++03 throwing an exception out of a destructor is not porblem and does not cause `std::terminate()` to be called (unless there is already an exception propogating). – Martin York Aug 10 '15 at 22:44
  • @LokiAstari that would be nice... but I don't want to leave hardware in a bad state. Or maybe I do, then I'll quit my job and leave some poor soul with a nightmare situation to debug. Muahaha – Erin Aug 10 '15 at 22:47
  • Just a small note... Since C++11 destructors default to noexcept so for this example to work as intended we need to use `~Bad() noexcept(false) { throw 1; }`. – Izaan Jun 06 '17 at 14:24
  • @LokiAstari Isn't execution stop at first throw in destructor and then came out of try block. why it is going in second line "throw 2" ? – EmptyData Sep 14 '17 at 04:17
  • @LokiAstari Isn't execution stop at first throw in destructor and then came out of try block. why it is going in second line "throw 2" ? – EmptyData Sep 14 '17 at 04:20
  • 1
    @EmptyData: You are correct for post C++11 (the default action of the destructor is to terminate on a throw as destructors are by default `noexcept`). This answer was written when C++03 was the standard. I will update to accommodate the change in language. – Martin York Sep 14 '17 at 14:38
  • 1
    @EmptyData Should now be accurate. – Martin York Sep 14 '17 at 14:48
  • 1
    Quote from the C++ standard (Working Draft, 15.2): '(...) 3. The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). [Note: So destructors should generally catch exceptions and not let them propagate out of the destructor. — end note"] ' – Sonic78 Oct 06 '17 at 07:26
  • @Sonic78 I can't find your quote in the standard. Please provide an exact section reference number and paragraph number. The current standard is here [n4659](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf) The closest I could find was: `when the destruction of an object during stack unwinding (18.2) terminates by throwing an exception,` in **Section 18.5.1** paragraph 1 point (1.4). But this is a note. You should note that notes are not normative and are there for clarification. In the default case your quote holds (as the destructor by default is `noexcept(true)` – Martin York Oct 06 '17 at 17:54
  • 2
    But a destructor can be explicitly marked `noexcept(false)` which leads to the behavior of C++03 were exceptions escaping a destructor did not call `terminate()`. You will also note that I added extensive comments in the example above to indicate this. – Martin York Oct 06 '17 at 17:55
  • @Loki Astari: Sorry looks like I used an older Working Draft and forgot to add the document number. The not still exists in the working draft with the number [N4296](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf) on page 417 (Section 15.2 paragraph 1). And yes it is only a note but IMHO a good advice. (Like your first sentence "Throwing an exception out of a destructor is dangerous.": so I gave +1.) Meanwhile I found a good blog entry in "Andrzej's C++ blog" (Destructors that throw)[https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/]. – Sonic78 Oct 06 '17 at 19:03
  • Totally agree destructors should not throw (when they do we should terminate) that is the best advice. But its not a requirement of the language. – Martin York Oct 06 '17 at 20:20
  • I think people haven't sufficiently considered having classes pass errors from destruction to a callback function as a potential alternative to manually calling `close()`. – Andy Feb 20 '18 at 09:25
  • 1
    @MartinYork I think there's a lesson here that RAII, being originally designed for local resource management, is unfortunately not quite as ideal for the many other types of cleanup it has come to be used for (though it's still superior to manual cleanup in a lot of ways). – Andy Feb 20 '18 at 09:29
  • "...anything dangerous should be done in..." ----------- Isn't true that every single god damn line of code could rise an exception? Even a simple addition can do that (Integer Overflow). – Z80 Jun 05 '20 at 11:06
  • @Migrate2Lazarusseemyprofile Integer overflow is not a "C++" exception. TO have a C++ exception some code has to explicitly execute a "throw" statement. So it is fairly easy to understand where and when exceptions can potentially happen. 1) A Throw statement 2) Anything that can contain a throw statement => A function/method not marked `noexcept` – Martin York Jun 05 '20 at 14:51
  • @MartinYork - Damn, I forgot that this feature ( http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsoverflowchecking_xml.html ) does not exist in C++. Sorry. – Z80 Jun 05 '20 at 15:20
  • 1
    @Migrate2Lazarusseemyprofile That has nothing to do with C++. Nowhere in the standard will you find anything like that. I am assuming you are confusing this documentation with the C++ standard. This would be in direct violation of standard C++. If you are going to quote something please use the standard. Here: https://stackoverflow.com/a/4653479/14065 The link to the current version is [n849](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf) – Martin York Jun 05 '20 at 15:34
  • @MartinYork - That's what I said in the previous comment: this feature (raising the EIntOverflow exception when range is violated) does not exist in C++. – Z80 Jun 05 '20 at 15:42
  • 1
    @Migrate2Lazarusseemyprofile Why even mention it. Why not simply delete you erroneous comment. – Martin York Jun 05 '20 at 15:43
  • @MartinYork - Too late. The information was "leaked" :) . Let the C guys know that other languages can handle that kind of exception/error. Maybe they will ask it to be implemented in C/++ later. – Z80 Jun 06 '20 at 16:59
60

Throwing out of a destructor can result in a crash, because this destructor might be called as part of "Stack unwinding". Stack unwinding is a procedure which takes place when an exception is thrown. In this procedure, all the objects that were pushed into the stack since the "try" and until the exception was thrown, will be terminated -> their destructors will be called. And during this procedure, another exception throw is not allowed, because it's not possible to handle two exceptions at a time, thus, this will provoke a call to abort(), the program will crash and the control will return to the OS.

Gal Goldman
  • 7,995
  • 11
  • 42
  • 45
  • 1
    can you please elaborate how abort() got called in the above situation. Means the control of execution was still with the C++ compiler – Krishna Oza Jan 24 '14 at 06:00
  • 2
    @Krishna_Oza: Quite simple: whenever an error is thrown, the code that raises an error checks some bit that indicates that the runtime system is in the process of stack unwinding (i.e., handling some other `throw` but not having found a `catch` block for it yet) in which case `std::terminate` (not `abort`) is called instead of raising a (new) exception (or continuing the stack unwinding). – Marc van Leeuwen Jun 06 '16 at 09:04
53

We have to differentiate here instead of blindly following general advice for specific cases.

Note that the following ignores the issue of containers of objects and what to do in the face of multiple d'tors of objects inside containers. (And it can be ignored partially, as some objects are just no good fit to put into a container.)

The whole problem becomes easier to think about when we split classes in two types. A class dtor can have two different responsibilities:

  • (R) release semantics (aka free that memory)
  • (C) commit semantics (aka flush file to disk)

If we view the question this way, then I think that it can be argued that (R) semantics should never cause an exception from a dtor as there is a) nothing we can do about it and b) many free-resource operations do not even provide for error checking, e.g. void free(void* p);.

Objects with (C) semantics, like a file object that needs to successfully flush it's data or a ("scope guarded") database connection that does a commit in the dtor are of a different kind: We can do something about the error (on the application level) and we really should not continue as if nothing happened.

If we follow the RAII route and allow for objects that have (C) semantics in their d'tors I think we then also have to allow for the odd case where such d'tors can throw. It follows that you should not put such objects into containers and it also follows that the program can still terminate() if a commit-dtor throws while another exception is active.


With regard to error handling (Commit / Rollback semantics) and exceptions, there is a good talk by one Andrei Alexandrescu: Error Handling in C++ / Declarative Control Flow (held at NDC 2014)

In the details, he explains how the Folly library implements an UncaughtExceptionCounter for their ScopeGuard tooling.

(I should note that others also had similar ideas.)

While the talk doesn't focus on throwing from a d'tor, it shows a tool that can be used today to get rid of the problems with when to throw from a d'tor.

In the future, there may be a std feature for this, see N3614, and a discussion about it.

Upd '17: The C++17 std feature for this is std::uncaught_exceptions afaikt. I'll quickly quote the cppref article:

Notes

An example where int-returning uncaught_exceptions is used is ... ... first creates a guard object and records the number of uncaught exceptions in its constructor. The output is performed by the guard object's destructor unless foo() throws (in which case the number of uncaught exceptions in the destructor is greater than what the constructor observed)

Martin Ba
  • 33,741
  • 27
  • 150
  • 304
  • 6
    Highly agree. And adding one more semantic (Ro) rollback semantics. Used commonly in scope guard. Like the case in my project where I defined a ON_SCOPE_EXIT macro. The case about rollback semantics is that anything meaningful could happen here. So we really shouldn't ignore the failure. – Weipeng L Aug 23 '13 at 06:55
  • I feel like the only reason we have commit semantics in destructors is that C++ doesn't support `finally`. – user541686 Dec 01 '15 at 05:22
  • @Mehrdad: `finally` *is* a dtor. It's always called, no matter what. For syntactic approximation of finally, see the various scope_guard implementations. Nowadays, with the machinery in place (even in the standard, is it C++14?) to detect whether the dtor is allowed to throw, it can even be made totally safe. – Martin Ba Dec 01 '15 at 11:06
  • 1
    @MartinBa: I think you missed the point of my comment, which is surprising since I was *agreeing* with your notion that (R) and (C) are different. I was trying to say that a dtor is inherently a tool for (R) and `finally` is inherently a tool for (C). If you don't see why: consider why it's legitimate to throw exceptions on top of each other in `finally` blocks, and why the same is *not* for destructors. (In some sense, it's a *data vs. control* thing. Destructors are for releasing data, `finally` is for releasing control. They are different; it's unfortunate that C++ ties them together.) – user541686 Dec 01 '15 at 11:18
  • @MartinBa: One RAII-like alternative to `try`/`finally` would have been for C++ to not only support destructors, but also *finalizers* as well. Finalizers would then be perfectly well allowed to throw exceptions on top of one another, because they would not affect the lifetimes of objects (those are governed by the destructors, which would run afterwards, regardless of whether the finalizers succeeded, as they cannot possibly fail). The fact that C++ treats finalization as destruction (or if you prefer to call it, commit as release) is the source of the problem. – user541686 Dec 01 '15 at 11:25
  • @Mehrdad - thanks for elaborating. I think the misunderstanding (if it is such) stems from the point that you seem to suggest that disallowing *or* allowing "throwing exceptions on top of each other" has a well-defined technical grounding. Whereas I suggest OTOH that it has not. There are arguments for both, and all C++, C# and Java have weaknesses in this area. The `terminate` thing in C++ is meh, but silently swallowing the first exception (Java, right?) is meh too. – Martin Ba Dec 01 '15 at 21:29
  • @MartinBa: By that, I was referring to throwing exceptions when one is already propagating. Why would it not have a technical grounding? Every language allows it outside of destructors (even C++ allows an exception to be thrown in a catch block); it's perfectly fine and well-defined. The only issue here is destructors; the reason C++ doesn't allow those is that it messes with the release of resources, which needs to happen even when finalization fails. I'm arguing release and finalization should be handled separately so that this isn't an issue anymore. – user541686 Dec 01 '15 at 23:43
  • 1
    @Mehrdad : Getting too long here. If you want, you can build up your arguments here: http://programmers.stackexchange.com/questions/304067/what-is-the-conceptual-difference-between-finally-and-a-destructor . Thanks. – Martin Ba Dec 02 '15 at 09:29
  • @MartinBa so what do you think about passing errors during destruction to a callback function? It seems like C++ programmers rarely remember that callback functions are a potential solution to all kinds of problems. – Andy Feb 20 '18 at 09:05
  • @MartinBa also thanks for putting the distinction between release and commit semantics in clear language, to stem the tide of "but how could you even handle any errors during destruction?" type responses. – Andy Feb 20 '18 at 09:11
  • @Mehrdad Throwing from a ctor affects the lifetime of the object, but not from a dtor. If it's OK to fail in a catch block or finally block I don't see why it's inherently bad in a dtor. The issues are pretty much the same. – curiousguy Jan 15 '19 at 00:18
  • @curiousguy: one of the promises of C++ is that automatic variables are destroyed when they go out of scope. If you throw from a constructor, there's no problem since the object is not yet constructed, but its fields are, so they can be destroyed. If you throw from a destructor, though, the object will not have been destroyed -- meaning it still exists -- while its fields are to be destroyed. This means you'll end up with an object that exists after its components are destroyed, which doesn't make sense and AFAIK doesn't occur in the language otherwise. – user541686 Jan 15 '19 at 06:56
  • @Mehrdad "_If you throw from a destructor, though, the object will not have been destroyed_" Why would that be the case? – curiousguy Jan 15 '19 at 15:25
  • @curiousguy: because the destructor would not have finished doing its job, which is destroying the object? – user541686 Jan 15 '19 at 16:56
  • @Mehrdad If the dtor doesn't take care to do a complete job, it means it isn't thread safe and shouldn't call any function that might throw an exception, just like any function with side effects that can't be left half done. Reallocation of a vector has that kind of issues: you can't leave the new range half constructed if one copy constructor fails. – curiousguy Jan 15 '19 at 20:02
  • @curiousguy: I don't get what those sentences have to do with any of your confusions that you were trying to clear up with me in the preceding discussion... I wasn't suggesting you should write a bad destructor; I was trying to explain why it semantically doesn't make sense to allow throwing from a destructor in the language. – user541686 Jan 15 '19 at 21:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186746/discussion-between-curiousguy-and-mehrdad). – curiousguy Jan 16 '19 at 00:07
22

The real question to ask yourself about throwing from a destructor is "What can the caller do with this?" Is there actually anything useful you can do with the exception, that would offset the dangers created by throwing from a destructor?

If I destroy a Foo object, and the Foo destructor tosses out an exception, what I can reasonably do with it? I can log it, or I can ignore it. That's all. I can't "fix" it, because the Foo object is already gone. Best case, I log the exception and continue as if nothing happened (or terminate the program). Is that really worth potentially causing undefined behavior by throwing from a destructor?

Derek Park
  • 43,840
  • 14
  • 54
  • 73
  • 12
    Just noticed ... throwing from a dtor is *never* Undefined Behaviour. Sure, it might call terminate(), but that is very well specified behaviour. – Martin Ba Dec 25 '13 at 20:09
  • 4
    `std::ofstream`'s destructor flushes and then closes the file. A disk full error could occur while flushing, which you can absolutely do something useful with: show the user an error dialog saying the disk is out of free space. – Andy Feb 20 '18 at 09:13
14

Its dangerous, but it also doesn't make sense from a readability/code understandability standpoint.

What you have to ask is in this situation

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

What should catch the exception? Should the caller of foo? Or should foo handle it? Why should the caller of foo care about some object internal to foo? There might be a way the language defines this to make sense, but its going to be unreadable and difficult to understand.

More importantly, where does the memory for Object go? Where does the memory the object owned go? Is it still allocated (ostensibly because the destructor failed)? Consider also the object was in stack space, so its obviously gone regardless.

Then consider this case

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

When the delete of obj3 fails, how do I actually delete in a way that is guaranteed to not fail? Its my memory dammit!

Now consider in the first code snippet Object goes away automatically because its on the stack while Object3 is on the heap. Since the pointer to Object3 is gone, you're kind of SOL. You have a memory leak.

Now one safe way to do things is the following

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

Also see this FAQ

Martin York
  • 234,851
  • 74
  • 306
  • 532
Doug T.
  • 59,839
  • 22
  • 131
  • 193
  • Resurrecting this answer, re: the first example, about `int foo()`, you can use a function-try-block to wrap the entire function foo in a try-catch block, including catching destructors, if you cared to do so. Still not the preferred approach, but it is a thing. – tyree731 Apr 15 '19 at 13:59
13

From the ISO draft for C++ (ISO/IEC JTC 1/SC 22 N 4411)

So destructors should generally catch exceptions and not let them propagate out of the destructor.

3 The process of calling destructors for automatic objects constructed on the path from a try block to a throw- expression is called “stack unwinding.” [ Note: If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. — end note ]

lothar
  • 18,633
  • 5
  • 43
  • 59
  • 1
    Did not answer the question - the OP is already aware of this. – Arafangion May 12 '09 at 09:19
  • 2
    @Arafangion I doubt that he was aware of this (std::terminate being called) as the accepted answer made exactly the same point. – lothar May 12 '09 at 15:10
  • @Arafangion as in some answers here some people mentioned that abort() being called; Or is it that the std::terminate in turns calls the abort() function. – Krishna Oza Jan 24 '14 at 07:12
8

I am in the group that considers that the "scoped guard" pattern throwing in the destructor is useful in many situations - particularly for unit tests. However, be aware that in C++11, throwing in a destructor results in a call to std::terminate since destructors are implicitly annotated with noexcept.

Andrzej Krzemieński has a great post on the topic of destructors that throw:

He points out that C++11 has a mechanism to override the default noexcept for destructors:

In C++11, a destructor is implicitly specified as noexcept. Even if you add no specification and define your destructor like this:

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

The compiler will still invisibly add specification noexcept to your destructor. And this means that the moment your destructor throws an exception, std::terminate will be called, even if there was no double-exception situation. If you are really determined to allow your destructors to throw, you will have to specify this explicitly; you have three options:

  • Explicitly specify your destructor as noexcept(false),
  • Inherit your class from another one that already specifies its destructor as noexcept(false).
  • Put a non-static data member in your class that already specifies its destructor as noexcept(false).

Finally, if you do decide to throw in the destructor, you should always be aware of the risk of a double-exception (throwing while the stack is being unwind because of an exception). This would cause a call to std::terminate and it is rarely what you want. To avoid this behaviour, you can simply check if there is already an exception before throwing a new one using std::uncaught_exception().

GaspardP
  • 3,239
  • 1
  • 18
  • 29
7

Your destructor might be executing inside a chain of other destructors. Throwing an exception that is not caught by your immediate caller can leave multiple objects in an inconsistent state, thus causing even more problems then ignoring the error in the cleanup operation.

Franci Penov
  • 71,783
  • 15
  • 124
  • 160
6

Everyone else has explained why throwing destructors are terrible... what can you do about it? If you're doing an operation that may fail, create a separate public method that performs cleanup and can throw arbitrary exceptions. In most cases, users will ignore that. If users want to monitor the success/failure of the cleanup, they can simply call the explicit cleanup routine.

For example:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};
Tom
  • 10,273
  • 3
  • 39
  • 49
  • I am looking for a solution but they are trying to explain what happened and why. Just want to make it clear is the close function get called inside the destructor? – Jason Liu Nov 15 '19 at 06:53
5

As an addition to the main answers, which are good, comprehensive and accurate, I would like to comment about the article you reference - the one that says "throwing exceptions in destructors is not so bad".

The article takes the line "what are the alternatives to throwing exceptions", and lists some problems with each of the alternatives. Having done so it concludes that because we can't find a problem-free alternative we should keep throwing exceptions.

The trouble is is that none of the problems it lists with the alternatives are anywhere near as bad as the exception behaviour, which, let's remember, is "undefined behaviour of your program". Some of the author's objections include "aesthetically ugly" and "encourage bad style". Now which would you rather have? A program with bad style, or one which exhibited undefined behaviour?

DJClayworth
  • 24,627
  • 8
  • 50
  • 71
3

So my question is this - if throwing from a destructor results in undefined behavior, how do you handle errors that occur during a destructor?

The main problem is this: you can't fail to fail. What does it mean to fail to fail, after all? If committing a transaction to a database fails, and it fails to fail (fails to rollback), what happens to the integrity of our data?

Since destructors are invoked for both normal and exceptional (fail) paths, they themselves cannot fail or else we're "failing to fail".

This is a conceptually difficult problem but often the solution is to just find a way to make sure that failing cannot fail. For example, a database might write changes prior to committing to an external data structure or file. If the transaction fails, then the file/data structure can be tossed away. All it has to then ensure is that committing the changes from that external structure/file an atomic transaction that can't fail.

The pragmatic solution is perhaps just make sure that the chances of failing on failure are astronomically improbable, since making things impossible to fail to fail can be almost impossible in some cases.

The most proper solution to me is to write your non-cleanup logic in a way such that the cleanup logic can't fail. For example, if you're tempted to create a new data structure in order to clean up an existing data structure, then perhaps you might seek to create that auxiliary structure in advance so that we no longer have to create it inside a destructor.

This is all much easier said than done, admittedly, but it's the only really proper way I see to go about it. Sometimes I think there should be an ability to write separate destructor logic for normal execution paths away from exceptional ones, since sometimes destructors feel a little bit like they have double the responsibilities by trying to handle both (an example is scope guards which require explicit dismissal; they wouldn't require this if they could differentiate exceptional destruction paths from non-exceptional ones).

Still the ultimate problem is that we can't fail to fail, and it's a hard conceptual design problem to solve perfectly in all cases. It does get easier if you don't get too wrapped up in complex control structures with tons of teeny objects interacting with each other, and instead model your designs in a slightly bulkier fashion (example: particle system with a destructor to destroy the entire particle system, not a separate non-trivial destructor per particle). When you model your designs at this kind of coarser level, you have less non-trivial destructors to deal with, and can also often afford whatever memory/processing overhead is required to make sure your destructors cannot fail.

And that's one of the easiest solutions naturally is to use destructors less often. In the particle example above, perhaps upon destroying/removing a particle, some things should be done that could fail for whatever reason. In that case, instead of invoking such logic through the particle's dtor which could be executed in an exceptional path, you could instead have it all done by the particle system when it removes a particle. Removing a particle might always be done during a non-exceptional path. If the system is destroyed, maybe it can just purge all particles and not bother with that individual particle removal logic which can fail, while the logic that can fail is only executed during the particle system's normal execution when it's removing one or more particles.

There are often solutions like that which crop up if you avoid dealing with lots of teeny objects with non-trivial destructors. Where you can get tangled up in a mess where it seems almost impossible to be exception-safety is when you do get tangled up in lots of teeny objects that all have non-trivial dtors.

It would help a lot if nothrow/noexcept actually translated into a compiler error if anything which specifies it (including virtual functions which should inherit the noexcept specification of its base class) attempted to invoke anything that could throw. This way we'd be able to catch all this stuff at compile-time if we actually write a destructor inadvertently which could throw.

  • 1
    Destruction is failure now? – curiousguy Jan 15 '19 at 00:16
  • 2
    I think he means that destructors are called during a failure, to cleanup that failure. So if a destructor is called during an active exception, then it is failing to cleanup from a previous failure. – user2445507 Mar 17 '19 at 00:41
2

Unlike constructors, where throwing exceptions can be a useful way to indicate that object creation succeeded, exceptions should not be thrown in destructors.

The problem occurs when an exception is thrown from a destructor during the stack unwinding process. If that happens, the compiler is put in a situation where it doesn’t know whether to continue the stack unwinding process or handle the new exception. The end result is that your program will be terminated immediately.

Consequently, the best course of action is just to abstain from using exceptions in destructors altogether. Write a message to a log file instead.

Devesh Agrawal
  • 7,964
  • 16
  • 67
  • 118
2

Q: So my question is this - if throwing from a destructor results in undefined behavior, how do you handle errors that occur during a destructor?

A: There are several options:

  1. Let the exceptions flow out of your destructor, regardless of what's going on elsewhere. And in doing so be aware (or even fearful) that std::terminate may follow.

  2. Never let exception flow out of your destructor. May be write to a log, some big red bad text if you can.

  3. my fave : If std::uncaught_exception returns false, let you exceptions flow out. If it returns true, then fall back to the logging approach.

But is it good to throw in d'tors?

I agree with most of the above that throwing is best avoided in destructor, where it can be. But sometimes you're best off accepting it can happen, and handle it well. I'd choose 3 above.

There are a few odd cases where its actually a great idea to throw from a destructor. Like the "must check" error code. This is a value type which is returned from a function. If the caller reads/checks the contained error code, the returned value destructs silently. But, if the returned error code has not been read by the time the return values goes out of scope, it will throw some exception, from its destructor.

MartinP
  • 550
  • 1
  • 6
  • 17
  • 4
    Your fave is something I tried recently, and it turns out you should *not* do it. http://www.gotw.ca/gotw/047.htm – GManNickG Mar 18 '10 at 15:03
1

I currently follow the policy (that so many are saying) that classes shouldn't actively throw exceptions from their destructors but should instead provide a public "close" method to perform the operation that could fail...

...but I do believe destructors for container-type classes, like a vector, should not mask exceptions thrown from classes they contain. In this case, I actually use a "free/close" method that calls itself recursively. Yes, I said recursively. There's a method to this madness. Exception propagation relies on there being a stack: If a single exception occurs, then both the remaining destructors will still run and the pending exception will propagate once the routine returns, which is great. If multiple exceptions occur, then (depending on the compiler) either that first exception will propagate or the program will terminate, which is okay. If so many exceptions occur that the recursion overflows the stack then something is seriously wrong, and someone's going to find out about it, which is also okay. Personally, I err on the side of errors blowing up rather than being hidden, secret, and insidious.

The point is that the container remains neutral, and it's up to the contained classes to decide whether they behave or misbehave with regard to throwing exceptions from their destructors.

Matthew
  • 11
  • 1
1

Martin Ba (above) is on the right track- you architect differently for RELEASE and COMMIT logic.

For Release:

You should eat any errors. You're freeing memory, closing connections, etc. Nobody else in the system should ever SEE those things again, and you're handing back resources to the OS. If it looks like you need real error handling here, its likely a consequence of design flaws in your object model.

For Commit:

This is where you want the same kind of RAII wrapper objects that things like std::lock_guard are providing for mutexes. With those you don't put the commit logic in the dtor AT ALL. You have a dedicated API for it, then wrapper objects that will RAII commit it in THEIR dtors and handle the errors there. Remember, you can CATCH exceptions in a destructor just fine; its issuing them that's deadly. This also lets you implement policy and different error handling just by building a different wrapper (e.g. std::unique_lock vs. std::lock_guard), and ensures you won't forget to call the commit logic- which is the only half-way decent justification for putting it in a dtor in the 1st place.

user3726672
  • 157
  • 2
0

Set an alarm event. Typically alarm events are better form of notifying failure while cleaning up objects

MRN
  • 1