10

This answer quotes C++11 Standard 3.8:

if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

The part about the destructor not being called is clear. Now suppose the skipped destructor had a side effect that should have affected the program behavior.

Why is the program behavior undefined now? Why wouldn't the side effects be skipped (since the destructor is not called) and the program run normally just without side effects applied?

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 2
    Interesting question. But why would someone not want to call the destructor on an object when it is not longer needed ? – ereOn Apr 02 '12 at 06:09
  • 1
    If the destructor in this case performs non trivial tasks for ex:Closing resource handles or setting state to something valid etc, then the destructor not being called will ofcourse affect the program and will result in Undefined Behavior.I don't understand the rationale for skipping side effects. – Alok Save Apr 02 '12 at 06:10
  • @ereOn: Singletons have been mentioned as a use case (however, this shouldn't be considered good design though). – Jesse Good Apr 02 '12 at 06:13
  • @ereOn: `std::cout` (and logging in general) are one of those special cases where you want them to be available for as long as the program runs. The obvious underlying issue is C++ absence of guarantee in the order of initialization and destruction of global variables, not calling the destructor of certain objects is a way to alleviate the Destruction Order Fiasco. – Matthieu M. Apr 02 '12 at 06:21

4 Answers4

4

The important part is the first part of that paragraph (emphasis mine):

A program may end the lifetime of any object by reusing the storage which the object occupies ...

If you simply reuse the storage for an object whose destructor has not been called, then you get undefined behaviour. For example, the object could have started a thread, or registered a callback, or some other action where an external component might expect the object to still exist.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    I disagree. The full quote adds *or by explicitly calling the destructor for an object of a class type with a non-trivial destructor*. So the destructor would be called in this case. – Matthieu M. Apr 02 '12 at 06:22
  • sorry, in which case? It does say **or**, so my quote above appears to stand on its own as well as yours. – Greg Hewgill Apr 02 '12 at 06:24
  • But that is the issue I am pointing at. Neither quote *stands on its own*. The Standard gives an alternative, but the choice is not ours. The *for an object of a class type with a non-trivial destructor* gives the criterion to select between the two branches of the **or**. And therefore any object with a non-trvial destructor (which is the interesting case here) will get its destructor called *before* its storage is reused. – Matthieu M. Apr 02 '12 at 06:34
3

In this case, we do have a precise answer. The specific line was introduced to resolve CWG 1116, "Aliasing of union members".

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • It seems CWG 1116 issue was raised when the line in the question was already in the standard. Its proposed resolution was to include an additional line starting with "If a program obtains storage for an object of a particular type A...". Did you perhaps mean some other issue? – max Nov 23 '19 at 19:08
1

Your question does not make sense.

Why wouldn't the side effects be skipped (since the destructor is not called) and the program run normally just without side effects applied?

They are skipped, because they would have been triggered by the destructor and it has not been called.

My reading of:

and any program that depends on the side effects produced by the destructor has undefined behavior.

is simple, I view it in light of RAII. Example:

#include "Object.hpp"

struct Manager: private boost::noncopyable {
  union Raw {
    char _[sizeof(Object)];
    Object o;
  };
  static Raw raw;

  Manager() { new (raw.o) Object(); }
  ~Manager() { raw.o.~Object(); }
};

Now, if I allocate a Manager, forgets to destroy it, and allocates a new one, I am in a pinch for I am overwriting the storage of the first created Object with a second one even though it is still "alive". This is undefined behavior.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
1

I believe this is put into the standard to allow for garbage collection. And to point out that C++ behaves differently that some other languages, by not calling destructors while collecting.

It specifically says that storage can be reused without calling the destructors of the objects in that memory area. If the program depends on the destructors being run, it will not work as expected.

If it doesn't depend on the destructors, all is fine.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203