Under what circumstances are C++ destructors not going to be called?

16,517

Solution 1

Are there any other circumstances where they[destructors] will not be called?

  1. Long jumps: these interfere with the natural stack unwinding process and often lead to undefined behavior in C++.
  2. Premature exits (you already pointed these out, though it's worth noting that throwing while already stack unwinding as a result of an exception being thrown leads to undefined behavior and this is why we should never throw out of dtors)
  3. Throwing from a constructor does not invoke the dtor for a class. This is why, if you allocate multiple memory blocks managed by several different pointers (and not smart pointers) in a ctor, you need to use function-level try blocks or avoid using the initializer list and have a try/catch block in the ctor body (or better yet, just use a smart pointer like scoped_ptr since any member successfully initialized so far in an initializer list will be destroyed even though the class dtor will not be called).
  4. As pointed out, failing to make a dtor virtual when a class is deleted through a base pointer could fail to invoke the subclass dtors (undefined behavior).
  5. Failing to call matching operator delete/delete[] for an operator new/new[] call (undefined behavior - may fail to invoke dtor).
  6. Failing to manually invoke the dtor when using placement new with a custom memory allocator in the deallocate section.
  7. Using functions like memcpy which only copies one memory block to another without invoking copy ctors. mem* functions are deadly in C++ as they bulldoze over the private data of a class, overwrite vtables, etc. The result is typically undefined behavior.
  8. Instantiation of some of smart pointers (auto_ptr) on an incomplete type, see this discussion

Solution 2

The C++ standard says nothing about how specific signals must be handled - many implementations may not support SIGINT, etc. Destructors will not be called if exit() or abort() or terminate() are called.

Edit: I've just had a quick search through the C++ Standard and I can't find anything that specifies how signals interact with object lifetimes - perhaps someone with better standards-fu than me could find something?

Further edit: While answering another question, I found this in the Standard:

On exit from a scope (however accomplished), destructors (12.4) are called for all constructed objects with automatic storage duration (3.7.2) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.

So it seems that destructors must be called on receipt of a signal.

Solution 3

Another case they won't be called is if you are using polymorphism and have not made your base destructors virtual.

Solution 4

A signal by itself won't affect the execution of the current thread and hence the invocation of destructors, because it is a different execution context with its own stack, where your objects do not exist. It's like an interrupt: it is handled somewhere outside of your execution context, and, if handled, the control is returned to your program.

Same as with multithreading, C++ the language does not know a notion of signals. These two are completely orthogonal to each other and are specified by two unrelated standards. How they interact is up to the implementation, as long as it does not break either of the standards.

As a side note, another case is when object's destructor won't be called is when its constructor throws an exception. Members' destructors will still be called, though.

Solution 5

abort terminates program without executing destructors for objects of automatic or static storage duration as Standard says. For other situations you should read implementation specific documents.

Share:
16,517

Related videos on Youtube

WilliamKF
Author by

WilliamKF

Updated on February 15, 2020

Comments

  • WilliamKF
    WilliamKF about 4 years

    I know that my destructors are called on normal unwind of stack and when exceptions are thrown, but not when exit() is called.

    Are there any other cases where my destructors are not going to get called? What about signals such as SIGINT or SIGSEGV? I presume that for SIGSEGV, they are not called, but for SIGNINT they are, how do I know which signals will unwind the stack?

    Are there any other circumstances where they will not be called?

    • Björn Pollex
      Björn Pollex almost 14 years
      As pointed out here, thedailywtf.com/Articles/My-Tales.aspx, you should also be aware the destructor will not be called when the power-plug is pulled ;).
    • karunski
      karunski almost 14 years
      SIGINT will not unwind the stack unless you install a signal handler that overrides the default behavior. By default SIGINT results in immediate program termination.
    • Marc van Leeuwen
      Marc van Leeuwen about 9 years
      Not posting this as an answer because it seems more like an oversight in the question. Destructors are only automatically called (in normal circumstances) at the end of the lifetime of objects with static, automatic or thread storage duration. For object with dynamic storage duration the destructor is only called when delete is called on a pointer to the object. So a destructor is not going to be called for dynamic objects for which delete is never called (whether because a memory leak makes this impossible, or by oversight).
  • Admin
    Admin almost 14 years
    In this case, you will get undefined behaviour.
  • karunski
    karunski almost 14 years
    On receipt of a signal, program control does not exit scope. So the quoted standard does not apply. The default behavior of POSIX signal handlers does not do any stack unwinding or destruction.
  • Admin
    Admin almost 14 years
    @karunski It certainly exits the scope if a signal handler is installed.
  • karunski
    karunski almost 14 years
    @Neil Butterworth Sure, but that doesn't happen by default. The more correct conclusion would be "Destructors must be called after a signal is handled (not received) and control returns to the point where the signal handler was invoked"
  • Admin
    Admin almost 14 years
    @karunski Do you have a Standard reference for the behaviour when a handler is not installed?
  • karunski
    karunski almost 14 years
    @Neil Butterworth Here's the POSIX standard signal handlers: opengroup.org/onlinepubs/007908775/xsh/signal.h.html
  • Admin
    Admin almost 14 years
    @karunski POSIX does not define the C++ language.
  • Fabio Fracassi
    Fabio Fracassi almost 14 years
    Nice list, but there is a flaw in point 3: you can actually put a try block around the initializer list, look up function level try blocks: struct X { X() try : x_(42) {} catch (...) {} private: int x_; }; Actually you can use this for any function like void foo() try {} catch (...) {} but some important compilers (VS2008, don't know if this is fixed in later versions) choke on it. The advice about smart pointers remains valid though.
  • stinky472
    stinky472 almost 14 years
    @Fabio Thanks Fabio, I'll point that out! I wasn't aware of these function-level try/catch blocks (prefer to just use RAII everywhere as it relieves headaches).
  • karunski
    karunski almost 14 years
    @Neil Butterworth The C++ language standard does not define signal behavior at all. POSIX does, and in practice unix-like operating systems follow POSIX.
  • Admin
    Admin almost 14 years
    @karunski But the question is about C++.
  • Shariful
    Shariful almost 14 years
    In general, a nice list. However, #4, and #5 are technically undefined behavior, so the standard has nothing to say about whether destructors get called or not. Most compilers will behave as you say (or may just crash).
  • Matthieu M.
    Matthieu M. almost 14 years
    The function try is illustrated in the GOTW #66 (just pointed it out to a colleague today... coincidences...)
  • stinky472
    stinky472 almost 14 years
    @KeithB Ah yes, I modified the answer to specify that these are undefined behaviors: we shouldn't expect anything sane to happen.
  • Trebor Rude
    Trebor Rude about 11 years
    Thanks for the tip on function level try blocks, @FabioFracassi. I can't believe I've been doing C++ for 15 years and either forgot about or never encountered that feature.
  • WilliamKF
    WilliamKF over 10 years
    This seems like a bug in that I doubt the C++ specification allows for such behavior.
  • Elmue
    Elmue over 10 years
    The question here is: "Under what circumstances destructors are not called". Even if this is a bug in Visual Studio, it is a valid answer to the question, because a bug is also a cirumstance. People come here from Google and some of them may experience just this specific problem without knowing if it is bug or not.
  • Marc van Leeuwen
    Marc van Leeuwen about 9 years
    stinky472 and @KeithB: You are wrong about #5: memory leaks are not undefined behavior
  • Marc van Leeuwen
    Marc van Leeuwen about 9 years
    Technically case #3 is a non-case, since the object for which the destructor is not called never even exists in the first place(though all "its" members do). Also I would have listed the case of objects that are variants of a union, and that do not get their destructors implicitly called when the lifetime of the union ends (though if care is taken a user-provided destructor of the union might do this). But maybe this falls under case #6 (I don't understand what you say about custom allocators here) since such variants will usually have to be created by placement-new.
  • Shariful
    Shariful about 9 years
    @MarcvanLeeuwen You are correct that memory leaks are not undefined behavior. However calling delete on memory that was allocated with new[] is undefined. See the answer to this question How serious is the new/delete operator mismatch error?
  • Marc van Leeuwen
    Marc van Leeuwen about 9 years
    @KeithB: Yes, and calling delete on a pointer that was obtained from malloc, or that points into non-dynamic memory in the first place is probably also UB. I don't see how that is related to my comment, or to this answer, though.
  • Kyle Strand
    Kyle Strand over 8 years
    MSVC with OMP directives does not guarantee the destruction of threadprivate objects. Does this fall under any of your list items, or is it separate? Is the MSVC behavior even conformant here?
  • Arvid
    Arvid over 7 years
    I don't believe throwing while unwinding because of another exception is undefined. (point 2). It's defined to call std::terminate
  • Craig M. Brandenburg
    Craig M. Brandenburg about 7 years
    @Arvid is correct; throwing while unwinding is not undefined behavior. See here: en.cppreference.com/w/cpp/error/terminate.