If you shouldn't throw exceptions in a destructor, how do you handle errors in it?

133,672

Solution 1

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

Solution 2

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.

Solution 3

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)

Solution 4

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?

Solution 5

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 ]

Share:
133,672

Related videos on Youtube

Greg Rogers
Author by

Greg Rogers

Updated on December 12, 2021

Comments

  • Greg Rogers
    Greg Rogers over 2 years

    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.

    • spraff
      spraff over 12 years
      "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).
    • curiousguy
      curiousguy over 12 years
      @spraff So any function with an empty post-condition could catch and discard all exceptions?
    • curiousguy
      curiousguy over 12 years
      > 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".
    • spraff
      spraff over 12 years
      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
      spraff over 12 years
      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.
    • Kos
      Kos over 12 years
      @spraff: Are you aware that what you said implies "throw away RAII"?
    • spraff
      spraff over 12 years
      Quite the opposite, actually.
    • Frunsi
      Frunsi over 11 years
      @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.
    • spraff
      spraff over 11 years
      @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.
    • Nicholas Wilson
      Nicholas Wilson over 10 years
      @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.
    • ThomasMcLeod
      ThomasMcLeod over 10 years
      This article is a more recent followup discussion on this problem cpp-next.com/archive/2012/08/evil-or-just-misunderstood
    • Jnana
      Jnana over 8 years
      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.
    • curiousguy
      curiousguy over 7 years
      @Jnana This "argument" is patently absurd. Not every object ends up in a container.
    • curiousguy
      curiousguy over 7 years
      @spraff "No, don't be lazy, write xyz() and keep the destructor clean of non-RAII logic" in the catch block?
    • choxsword
      choxsword almost 3 years
      @spraff what if I use exception to check pre-conditions? Is there any problem?
    • spraff
      spraff almost 3 years
      @scottxiao Destructors have no preconditions other than that the object exists.
    • choxsword
      choxsword almost 3 years
      @spraff but you said that exception should be thrown if and only if a function's postconditions cannot be met , which refers not only desctructors but all functions.
    • spraff
      spraff almost 3 years
      @scottxiao Sure, but any destructor which can possibly fail to complete is already broken. Things which can fail should be in functions which get called during the object's lifetime. When an object is ceasing to exist, failure is simply not an option. You shouldn't be in this situation in the first place. Do all nontrivial cleanup before destroying the object. If you have weird fancy failure-prone code living in destructors, you have major design problems which shouldn't be resolved by saying "oh screw it I'll just throw during the destructor"
    • spraff
      spraff almost 3 years
      It's not the language's job to permit you to do illogical stuff, it's the languages job to prevent you from exactly that.
    • Admin
      Admin about 2 years
      @spraff well then C++ failed spectacularly because it is the only programming language where I even considered doing something dumb. To avoid something even dumber... like bitshifting parts of strings into stdout, or having to go back to C style goto cleanup mode for things like files just because RAII is inherently broken. And yes, file descriptor IS a resource, in fact, it is a handle to a resource. The fact that you can't handle it properly with RAII looks to me like my filesystem isn't what's actually broken here.
    • spraff
      spraff about 2 years
      @Sahsahae You can close a file handle with RAII so I don't know what you mean. And yes you can force C++ to do illogical things, the fact that you have to bend over backwards to do it is a good thing.
    • Admin
      Admin about 2 years
      @spraff how is it a good thing exactly? You cannot safely close a file using RAII. It is designed this way, everyone using it also agrees with this way. They even claim it is absolutely wrong to do it any other way. The proper way I'm talking about would be for file destructor to throw an exception so code following it doesn't do something completely stupid, like assume that file was written to disk. Early termination is better than trashing an important file just because someone forgot some call, while using language that touts a feature that supposedly alleviates that responsibility.
    • spraff
      spraff about 2 years
      @Sahsahae You can safely close a file using RAII, for example by wrapping int close(int fd) from <unistd.h>. This function is guaranteed to return without throwing an exception so you're all good. If you were to wrap close with throwing_close(int fd){if close(fd)<0 throw ...} then that would be bad. Don't do that.
    • spraff
      spraff about 2 years
      @Sahsahae To elaborate to address the original question, your RAII destructor wrapping close should check the return value and report an error, or silently leave the resource dangling, or reattempt, or simply kill the process, whatever makes sense for you, but it should not fail to return from the destructor because this leads to UB.
  • Erik Forbes
    Erik Forbes over 15 years
    "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.
  • Martin York
    Martin York over 15 years
    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.
  • mekanik
    mekanik over 15 years
    When the application goes down, then it's up to the OS to handle cleaning up any leftover resources.
  • Tom
    Tom over 15 years
    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.
  • Arafangion
    Arafangion almost 15 years
    Did not answer the question - the OP is already aware of this.
  • Nathaniel Sharp
    Nathaniel Sharp almost 15 years
    @Arafangion I doubt that he was aware of this (std::terminate being called) as the accepted answer made exactly the same point.
  • GManNickG
    GManNickG about 14 years
    Your fave is something I tried recently, and it turns out you should not do it. gotw.ca/gotw/047.htm
  • Admin
    Admin almost 13 years
    @Martin: If the application is closing DB connections in the destructor, it may not be properly designed.
  • curiousguy
    curiousguy over 12 years
    @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.
  • Adrien Plisson
    Adrien Plisson over 12 years
    @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.
  • Weipeng L
    Weipeng L over 10 years
    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.
  • Martin Ba
    Martin Ba over 10 years
    Just noticed ... throwing from a dtor is never Undefined Behaviour. Sure, it might call terminate(), but that is very well specified behaviour.
  • Krishna Oza
    Krishna Oza over 10 years
    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
    Krishna Oza over 10 years
    @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.
  • doug65536
    doug65536 about 10 years
    @LokiAstari The transport protocol you are using to communicate with a spacecraft can't handle a dropped connection? Ok...
  • Martin York
    Martin York about 10 years
    @doug65536: Is that an assumption you want to make (with you $100 Billion ship on its way to Mars)?
  • Martin York
    Martin York over 9 years
    @Deduplicator: What does this add to the current discussion.
  • Deduplicator
    Deduplicator over 9 years
    @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.
  • Aaron
    Aaron over 8 years
    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.
  • Martin York
    Martin York over 8 years
    @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
    Martin York over 8 years
    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.
  • Aaron
    Aaron over 8 years
    @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.
  • Martin York
    Martin York over 8 years
    @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).
  • Aaron
    Aaron over 8 years
    @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
  • user541686
    user541686 over 8 years
    I feel like the only reason we have commit semantics in destructors is that C++ doesn't support finally.
  • Martin Ba
    Martin Ba over 8 years
    @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.
  • user541686
    user541686 over 8 years
    @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
    user541686 over 8 years
    @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.
  • Martin Ba
    Martin Ba over 8 years
    @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.
  • user541686
    user541686 over 8 years
    @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.
  • Martin Ba
    Martin Ba over 8 years
    @Mehrdad : Getting too long here. If you want, you can build up your arguments here: programmers.stackexchange.com/questions/304067/… . Thanks.
  • Marc van Leeuwen
    Marc van Leeuwen almost 8 years
    @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
    Marc van Leeuwen almost 8 years
    Not undefined behaviour, but rather immediate termination.
  • DJClayworth
    DJClayworth almost 8 years
    The standard says 'undefined behaviour'. That behaviour is frequently termination but it isn't always.
  • Marc van Leeuwen
    Marc van Leeuwen almost 8 years
    No, read [except.terminate] in Exception handling->Special functions (which is 15.5.1 in my copy of the standard, but its numbering is probably outdated).
  • Izaan
    Izaan almost 7 years
    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; }.
  • EmptyData
    EmptyData over 6 years
    @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
    EmptyData over 6 years
    @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" ?
  • Martin York
    Martin York over 6 years
    @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
    Martin York over 6 years
    @EmptyData Should now be accurate.
  • Sonic78
    Sonic78 over 6 years
    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"] '
  • Martin York
    Martin York over 6 years
    @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 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
    Martin York over 6 years
    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.
  • Sonic78
    Sonic78 over 6 years
    @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 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)[akrzemi1.wordpress.com/2011/09/21/destructors-that-th‌​row/].
  • Martin York
    Martin York over 6 years
    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.
  • Andy
    Andy about 6 years
    @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
    Andy about 6 years
    @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
    Andy about 6 years
    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
    Andy about 6 years
    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
    Andy about 6 years
    @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).
  • curiousguy
    curiousguy over 5 years
    Destruction is failure now?
  • curiousguy
    curiousguy over 5 years
    @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.
  • user541686
    user541686 over 5 years
    @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.
  • curiousguy
    curiousguy over 5 years
    @Mehrdad "If you throw from a destructor, though, the object will not have been destroyed" Why would that be the case?
  • user541686
    user541686 over 5 years
    @curiousguy: because the destructor would not have finished doing its job, which is destroying the object?
  • curiousguy
    curiousguy over 5 years
    @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.
  • user541686
    user541686 over 5 years
    @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.
  • curiousguy
    curiousguy over 5 years
  • user2445507
    user2445507 about 5 years
    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.
  • tyree731
    tyree731 about 5 years
    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.
  • Konard
    Konard almost 5 years
    Writing a message to the log file can cause an exception.
  • Jason Liu
    Jason Liu over 4 years
    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?
  • Server Overflow
    Server Overflow almost 4 years
    "...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).
  • Martin York
    Martin York almost 4 years
    @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
  • Server Overflow
    Server Overflow almost 4 years
    @MartinYork - Damn, I forgot that this feature ( docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/… ) does not exist in C++. Sorry.
  • Martin York
    Martin York almost 4 years
    @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: stackoverflow.com/a/4653479/14065 The link to the current version is n849
  • Server Overflow
    Server Overflow almost 4 years
    @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++.
  • Martin York
    Martin York almost 4 years
    @Migrate2Lazarusseemyprofile Why even mention it. Why not simply delete you erroneous comment.
  • Server Overflow
    Server Overflow almost 4 years
    @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.
  • einpoklum
    einpoklum over 2 years
    Disagree about the rationale for (R) semantics: "(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." About (b): For free ops which can't fail/don't report errors, this question doesn't come up. It comes up when these ops do report errors. In such cases, there are lots of things one can do about it - but, of course, not within the destructor, which has very little context. And...
  • einpoklum
    einpoklum over 2 years
    ... And when you need to tell outside code that there was a problem which you can't handle yourself, throwing an exception is how you do it (especially when you cant return an error status).
  • einpoklum
    einpoklum over 2 years
    First of all, logging is already enough to merit throwing an exception (had it not been for the difficulty of stack-unwinding destruction). Logging errors can be critically important, but the destructed object cannot, in general, know how to log errors. Additionally, there may be other things which need to / should be done after such an error, e.g. also releasing other resources or perhaps re-establishing a state in which such errors would no longer occur (e.g. a network connection). So, bottom line: The caller can do lots.
  • einpoklum
    einpoklum over 2 years
    "What should catch the exception? Should the caller of foo?" Yes; or it could let it propagate up. "Why should the caller of foo care about some object internal to foo?" The caller of foo does know about the internal object, it will know that foo() threw an exception, somehow.
  • einpoklum
    einpoklum over 2 years
    I can't believe an answer actually answering OP's question is ranked so low. +1.
  • Martin Ba
    Martin Ba over 2 years
    @einpoklum - Good points. I created a chat room for this: chat.stackoverflow.com/rooms/240066/rollback-does-not-throw
  • Admin
    Admin about 2 years
    @Andy also it is a common strategy to flush to a copy of sensitive file and then move said modified file onto the original. You can probably imagine how you can lose data just because you went ahead and ignored fsync error, which lead to you moving a broken file onto the original. Even a hard termination of a program the moment fsync failed would've been safer than simply losing everything. But you can make a backup beforehand... Which will fail too if you don't make sure fsync succeeded. You should never ignore these kinds of errors unless what you're doing isn't very important.
  • Sahsahae
    Sahsahae about 2 years
    "Why should the caller of foo care about some object internal to foo?" you're right, they shouldn't, but as responsible C++ programmer I assume that they will, they always do, you can tell them whatever you want, they don't care. Hyrum's Law. C++'s terrible design can be blamed on this.
  • Sahsahae
    Sahsahae about 2 years
    @JasonLiu no, close is in fact completely separate from destructor and has very little to absolutely no coupling. It is pretty much a switch that causes certain part of code that throws to be ran prematurely. In destructor, you check if it already ran, for example, if it's a file, you skip closing it, it's already closed in some predictable state. But this pretty much throws away the only real thing C++ has over C... RAII. And you write twice as much code. You can see this in fstream class, if you don't close file manually, destructor closes it and ignores all errors.
  • Admin
    Admin about 2 years
    @MarcvanLeeuwen thank you for that point, I found myself in a spot where I could easily prevent double throw in some part of a code and it would've been a lot cleaner if I simply moved the throw into the destructor considering different kinds of usage patterns of that particular class don't make sense anyway, but didn't because it's "bad practice". Wish people experimented with this concept a bit more, because there are cases where losing 15 minutes of work due to an unexpected termination feels a lot better than I don't know, whole disk being destroyed because I forgot to call some function.
  • Admin
    Admin about 2 years
    @user2445507 complete nonsense. Destructors are small part of the program, the last thing they should be aware of is other destructors, or whether they were called because object gracefully fell out of scope or because stack was unwound prematurely... And that's why the program is terminated if you throw during stack unwinding, precisely because of the fact that they are not aware, should not be.
  • Sahsahae
    Sahsahae about 2 years
    @einpoklum and everything else is so vague too, "you shouldn't throw in destructor, but I'm not going to provide a proper solution"... I'm not sure anymore whether this is cargo cultism or simply people who have no idea trying to answer the question by improvising...
  • Admin
    Admin about 2 years
    @Konard and throwing in destructor can't... I am still looking for a real explanation why everyone is so against this, because so far in every scenario where I considered doing this, it actually makes sense to me.
  • Konard
    Konard about 2 years
    @Sahsahae I think that if you use try and catch around log operation in destructor it should be ok.
  • Admin
    Admin about 2 years
    @Konard but the issue is that if it throws you can't log it or do anything, it's useless, just like the logging (and then just ignoring the error) itself. Exceptions are exceptional, but control flow nonetheless, and ignoring it only leads to bugs, some of which aren't recoverable from even if you read the logs and noticed the issue, such as corrupted files or databases...