26

Note: I've seen similar questions, but none of the answers are precise enough, so I'm asking this myself.

This is a very nitpicky "language-lawyer" question; I'm looking for an authoritative answer.

The C++ standard says:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression 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.

I simply do not understand what "depends on the side effects" means.

The general question is:

Is forgetting to call a destructor any different than forgetting to call an ordinary function with the same body?

A specific example to illustrate my point is:

Consider a program like this below. Also consider the obvious variations (e.g. what if I don't construct an object on top of another one but I still forget to call the destructor, what if I don't print the output to observe it, etc.):

#include <math.h>
#include <stdio.h>

struct MakeRandom
{
    int *p;
    MakeRandom(int *p) : p(p) { *p = rand(); }
    ~MakeRandom() { *p ^= rand(); }
};

int main()
{
    srand((unsigned) time(NULL));        // Set a random seed... not so important
    // In C++11 we could use std::random_xyz instead, that's not the point

    int x = 0;
    MakeRandom *r = new MakeRandom(&x);  // Oops, forgot to call the destructor
    new (r) MakeRandom(&x);              // Heck, I'll make another object on top
    r->~MakeRandom();                    // I'll remember to destroy this one!
    printf("%d", x);                     // ... so is this undefined behavior!?!
    // If it's indeed UB: now what if I didn't print anything?
}

It seems ridiculous to me to say this exhibits "undefined behavior", because x is already random -- and therefore XORing it another random number cannot really make the program more "undefined" than before, can it?

Furthermore, at what point is it correct to say the program "depends" on the destructor? Does it do so if the value was random -- or in general, if there is no way for me to distinguish the destructor from running vs. not running? What if I never read the value? Basically:

Under which condition(s), if any, does this program exhibit Undefined Behavior?

Exactly which expression(s) or statement(s) cause this, and why?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • "*It seems ridiculous...*" - You seem to be implying that a program that relies on non-deterministic input (in this case `time`) cannot, by definition, exhibit undefined behaviour... – Oliver Charlesworth Jan 14 '14 at 09:47
  • 1
    @OliCharlesworth: No, that's definitely not what I'm saying. The value of the input might be undefined there, but that doesn't mean the behavior is undefined. Here, I *expect* the program *must* print an integer -- it can't suddenly crash, for example. I expect this should happen regardless of whether or not the destructor is called. However, if forgetting to call the destructor indeed results in undefined *behavior*, then the program can crash (or do anything else). I don't think that makes sense, since the program doesn't *depend* on the number having any particular value... – user541686 Jan 14 '14 at 10:07
  • You're trying to tease out whether there's something about ctors and dtors that's special that renders this code inequivalent to a memory allocation, a couple of assignments, an xor, and a printf, right? – tmyklebu Jan 15 '14 at 22:42
  • @tmyklebu: Not just "whether" there's something special, but rather *what* specifically is special. – user541686 Jan 15 '14 at 22:56
  • 1
    My opinion ain't worth much here, but it would really disturb me if your code exhibited UB. My reading of the paragraph is that, if your program depends on the dtor being called before exit, your program sucks. That is, I think they meant to say "it's UB if you depend on the dtor being called before exit," not "it's UB if you rely on the dtor working at all." But I don't know why it had to be said at all... – tmyklebu Jan 16 '14 at 01:00
  • @tmyklebu: How is that different from a situation in which we just forgot to call a regular function? – user541686 Jan 16 '14 at 01:07
  • @Mehrdad: It's not, AFAIK. Like I said, I don't know why they said anything at all. I think "side-effects produced by the destructor" was meant to be "side-effects produced by a hypothetical destructor call that occurs just before program termination." – tmyklebu Jan 16 '14 at 02:04
  • (Yet again, IANALL. But I can't imagine why they'd want to disallow your code.) – tmyklebu Jan 16 '14 at 02:40
  • In http://stackoverflow.com/questions/9971559/why-is-not-deleting-an-object-that-has-a-destructor-with-a-side-effect-undefined, @msalters points out that the reason for this formulation is to resolve an issue with aliasing of union members. See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1116 – flup Jan 20 '14 at 21:29
  • @flup: That is very interesting... I'm having trouble understanding how it relates to destructors though. The entire proposal seems to exclude any destructors entirely, so what is it saying about them exactly? – user541686 Jan 20 '14 at 21:36
  • The clarification they proposed to add was: If a program obtains storage for an object of a particular type A (e.g. with a variable definition or new-expression) and later reuses that storage for an object of another type B such that accessing the stored value of the B object through a glvalue of type A would have undefined behavior (3.10 [basic.lval]), the behavior is undefined. I think that's where the destructor gets forgotten, between newing the memory for A and reusing it for B. – flup Jan 21 '14 at 08:02
  • @flup: But what's that saying about the destructor? If we had defined an empty non-trivial destructor and called it properly, then there would be no problem?! It seems to go completely against the notion of "depending on the effects of the destructor"... maybe I'm still missing the point? – user541686 Jan 21 '14 at 08:34
  • Not very lawyerly way to put it, I am aware how this sounds, but the side-effect of calling the destructor, even if it is otherwise empty, is that the memory loses some kind of "A-ness" it acquired when it got newed? :) – flup Jan 21 '14 at 22:58
  • For me: "any program that depends on the **side effects**" is likely written incorrectly. I would not consider what takes place in a destructor (E.G: release memory, update reference counters, etc) to be **side effects**. Dictionary talks of a side effect as "often harmful and unwanted effects". That is not the way to describe what goes on in a destructor and a program would certainly not "depend" on such an effect. I believe "side" should be removed. – Chris Jan 24 '14 at 20:36

13 Answers13

7

I simply do not understand what "depends on the side effects" means.

It means that it depends on something the destructor is doing. In your example, modifying *p or not modifying it. You have that dependency in your code, as the output would differ if the dctor wouldn't get called.

In your current code, the number that is printed, might not be the same number that would have returned by the second rand() call. Your program invokes undefined behavior, but it's just that UB here has no ill effect.

If you wouldn't print the value (or otherwise read it), then there wouldn't be any dependency on the side effects of the dcor, and thus no UB.

So:

Is forgetting to call a destructor any different than forgetting to call an ordinary function with the same body?

Nope, it's not any different in this regard. If you depend on it being called, you must make sure it's called, otherwise your dependency is not satisfied.

Furthermore, at what point is it correct to say the program "depends" on the destructor? Does it do so if the value was random -- or in general, if there is no way for me to distinguish the destructor from running vs. not running?

Random or not doesn't matter, because the code depends on the variable being written to. Just because it's difficult to predict what the new value is doesn't mean there's no dependency.

What if I never read the value?

Then there's no UB, as the code has no dependency on the variable after it was written to.

Under which condition(s), if any, does this program exhibit Undefined Behavior?

There are no conditions. It's always UB.

Exactly which expression(s) or statement(s) cause this, and why?

The expression:

printf("%d", x);

because it introduces the dependency on the affected variable.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • Does the current program depend on the side effects (if any) of the destructor? – user541686 Jan 14 '14 at 11:20
  • @Mehrdad Yeah, it does. But it has no ill effects in this case, because the program doesn't care about the dependency. – Nikos C. Jan 14 '14 at 11:26
  • That doesn't make sense. If it can exhibit UB, then it means it can do anything, like crash. That's what it means for it to be undefined. The fact that you're saying it *must* output a number means it's not UB... – user541686 Jan 14 '14 at 11:26
  • 3
    @Mehrdad The standard doesn't say it might crash. That's just something we usually use to explain UB to others. It's safe to assume that this won't crash. You are forgetting that the standard is not a compiler. It's just text on paper. – Nikos C. Jan 14 '14 at 11:28
  • 1
    I know, but if it's undefined then it *could* crash, even if it "probably" won't. But you're implying it *must* output a number -- which means it would be *illegal* for it to crash. The statements can't be both be true. Which is it? – user541686 Jan 14 '14 at 11:29
  • 1
    @Mehrdad Do you want me to look up the compiler sources and give you a guarantee that you won't crash? That would be too much work. You can do that yourself. – Nikos C. Jan 14 '14 at 11:30
  • 1
    No, I'm not asking about a specific compiler. I'm asking what an arbitrary compiler is allowed to do according to the standard. – user541686 Jan 14 '14 at 11:30
  • 6
    @Mehrdad The standard doesn't say what it's allowed to do and what not in case of UB. You can assume however what it might do in this case. And in this case, crashing seems extremely far fetched. However, your question was not about what UB is. It was about whether this particular programs exhibits UB, and I answered that, I believe. What UB means is another question entirely. Please open a new one if you want to ask about that. – Nikos C. Jan 14 '14 at 11:32
  • I don't think you answered it... but I'm not sure how else to phrase the question for you so I'll just leave this as is. – user541686 Jan 14 '14 at 11:34
  • 3
    @Mehrdad it's always possible to construct a test case for UB that doesn't exhibit any ill effects. The more important question is whether you can construct an example that *does*. Or even more importantly, whether a different or newer compiler might exhibit different behavior. – Mark Ransom Jan 14 '14 at 13:03
  • +1 solid answer, but re "forgetting to call a constructor any different [vs other functions]" you say no - which makes sense given your general perspective that the behaviour here is understandable even though technically undefined, but I think it's worth at least a nod to that distinction in this part of the answer... for other function's it's not even nominally U.B. just because you have this kind of dependency. – Tony Delroy Jun 10 '14 at 14:42
6

This makes sense if you accept that the Standard is requiring allocation to be balanced by destruction in the case where destructors affect program behavior. I.e. the only plausible interpretation is that if a program

  • ever fails to call the destructor (perhaps indirectly through delete) on an object and
  • said destructor has side-effects,

then the program is doomed to the land of UB. (OTOH, if the destructor doesn't affect program behavior, then you are off the hook. You can skip the call.)

Note added Side effects are discussed in this SO article, and I'll not repeat that here. A conservative inference is that "program ... depends on destructor" is equivalent to "destructor has a side-effect."

Additional note However, the Standard seems to allow for a more liberal interpretation. It does not formally define dependence of a program. (It does define a specific quality of expressions as dependence-carrying, but this does not apply here.) Yet in over 100 uses of derivatives of "A depends on B" and "A has a dependency on B," it employs the conventional sense of the word: a variation in B leads directly to variation in A. Consequently, it does not seem a leap to infer that a program P depends on side effect E to the extent that performance or non-performance of E results in a variation in observable behavior during execution of P. Here we are on solid ground. The meaning of a program - its semantics - is equivalent under the Standard to its observable behavior during execution, and this is clearly defined.

The least requirements on a conforming implementation are:

  • Access to volatile objects are evaluated strictly according to the rules of the abstract machine.

  • At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.

  • The input and output dynamics of interactive devices shall take place in such a fashion that prompting output is actually delivered before a program waits for input. What constitutes an interactive device is implementation-defined.

These collectively are referred to as the observable behavior of the program.

Thus, by the Standard's conventions, if a destructor's side effect would ultimately affect volatile storage access, input, or output, and that destructor is never called, the program has UB.

Put yet another way: If your destructors do significant things and aren't consistently called, your program (says the Standard) ought to be considered, and is hereby declared, useless.

Is this overly restrictive, nay pedantic, for a language standard? (After all, the Standard prevents the side-effect from occurring due to an implicit destructor call and then drubs you if the destructor would have caused a variation in observable behavior if it had been called!) Perhaps so. But it does make sense as a way to insist on well-formed programs.

Community
  • 1
  • 1
Gene
  • 46,253
  • 4
  • 58
  • 96
  • Nope this still isn't clear at all. The entire question hinges on what *"affect program behavior"* means (or what "side effect" means). Does it mean "I can distinguish a program with the given destructor from a program with an empty destructor" (i.e. the difference must be observable)? Or does it mean "the destructor writes to memory locations which are subsequently read from" (even if they don't affect program output)? Or something else? – user541686 Jan 21 '14 at 06:21
  • @Mehrdad Please see addition. Side-effect is a well-defined term. – Gene Jan 21 '14 at 13:25
  • Hmm, so that makes the answer clearer, but I'm not convinced it's correct. Is *depending on a destructor* really equivalent to *destructor has a side-effect*? That answer says *"modifying an object"* is a "side-effect". But consider `class S { unsigned char x; public: ~S() { ++x; } };`... the destructor here is clearly modifying an object -- hence that's a "side effect" with the given definition -- yet I'm pretty sure no program could "depend" on this side effect in any reasonable sense of the term. What am I missing? – user541686 Jan 21 '14 at 15:25
  • @Mehrdad You really are a language lawyer! To carry this to its ultimate end, I added more new above. – Gene Jan 22 '14 at 04:01
  • Haha yeah. Usually I'm not so lawyer-y but on this particular question it really matters to me if I can rely on destructors behaving like regular functions or not. +1, I think this is a reasonably consistent answer now. :) Will probably wait a bit before rewarding though, to see if anyone has insights to add. – user541686 Jan 22 '14 at 04:31
  • Suppose a program would print out either "A" or "a" if a destructor weren't run, with the choice being a result of Unspecified Behavior. Suppose further that the destructor would switch the chosen output (output "A" if it would have output "a" and vice versa). If the destructor does not execute, the program may legitimately output either "A" or "a". Likewise if it does. Would the program "depend" upon the destructor running? What if output was redirected to `/dev/null`? – supercat Apr 17 '15 at 21:56
4

This is indeed not a very well defined thing in the standard, but I would interpret "depends on" as meaning "the behavior under the rules of the abstract machine is affected".

This behavior consists of the sequence of reads and writes to volatile variables and the calls to library I/O functions (which includes at least the I/O functions of the standard library like printf, but may also include any number of additional functions in any given implementation, e.g. WinAPI functions). See 1.9/9 for the exact wording.

So the behavior is undefined if execution of the destructor or lack thereof affects this behavior. In your example, whether the destructor is executed or not affects the value of x, but that store is dead anyway, since the next constructor call overwrites it, so the compiler could actually optimize it away (and chances are, it will). But more importantly, the call to rand() affects the internal state of the RNG, which influences the values returned by rand() in the other object's constructor and destructor, so it does affect the final value of x. It's "random" (pseudo-random) either way, but it would be a different value. Then you print x, turning that modification into observable behavior, thus making the program undefined.

If you never did anything observable with x or the RNG state, the observable behavior would be unchanged independent of whether the destructor is called or not, so it wouldn't be undefined.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • +1 also a decent answer. I also totally forgot about `srand` when I wrote my post, so that's a good point about the seed. – user541686 Jan 22 '14 at 04:34
  • IMHO the Standard is a bit murky here. It might be trying to say that a program might be in a state where Schrödinger's Destructor had simultaneously been called and not called. If the destructor would have changed the value of a flag from 0 to 1, tests for the flag might arbitrarily *and independently* return true or false. The Standard has no description short of UB for such a state of affairs, but one could still reason about code's behavior in situations where all combinations of the flag reading true and false could yield the same output. – supercat Apr 03 '17 at 19:30
4

For this answer, I will be using a 2012 C++11 release of the C++ standard, which can be found here (C++ standard), because this is freely available and up to date.

The following three terms used in your question occur as followed:

  1. Destructor - 385 times
  2. Side effect - 71 times
  3. Depends - 41 times

Sadly "depends on the side effect" appears only once, and DEPENDS ON is not an RFC standardized identifier like SHALL, so it's rather hard to pin down what depends means.

Depends on

Let's take an "activist judge" approach, and assume that "depends", "dependency", and "depending" are all used in a similar context in this document, that is, that the language was used to convey a broad idea rather than to convey a legalease concept.

Then we can analyze this portion of page 1194:

17.6.3.2
Effect on original feature: Function swap moved to a different header
Rationale: Remove dependency on for swap.
Effect on original feature: Valid C++ 2003 code that has been compiled expecting swap to be in < algorithm > may have to instead include < utility >.

This portion indicates a strict sort of dependency; you originally needed to include to get std::swap. "depends on" therefore indicated a strict requirement, a necessity so to speak, in the sense that there is not sufficient context without the requirement to proceed; failure will occur without the dependency.

I chose this passage because it conveys the intended meaning as clearly as possible; other passages are more verbose, but they all include a similar meaning: necessity.

Therefore, a "depends on" relationship means that the thing being depended on is required for the depending item to make sense, be whole and complete, and be usable in a context.

To cut through that legalese red tape, this means A depends on B means A requires B. This is basically what you'd understand "depend" to mean if you looked it up in a dictionary or spoke it in a sentence.

Side effect

This is more strictly defined, on page 10:

Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

This means that anything which results in a change to the environment (such as RAM, network IO, variables, etc etc) are side effects. This neatly fits with the notion of impurity/purity from functional languages, which is clearly what was intended. Note that the C++ standard does not require that such side effects be observable; modifying a variable in any way, even if that variable is never looked at, is still a side effect.

However, due to the "as if" rule, such unobservable side effects may be removed, page 8:

A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).

Depends on the side effects

Putting these two definitions together, we can now define this phrase: something depends on the side effects when those changes to the execution environment are required in order to satisfy the senseful, whole, and complete operations of the program. If, without the side effects, some constraint is not satisfied that is required for the program to operate in a standard compliant way, we can say that it depends on the side effects.

A simple example to illustrate this would be, as stated in another answer, a lock. A program that uses locks depends on the side effect of the lock, notably, the side effect of providing a serialized access pattern to some resource (simplified). If this side effect is violated, the constraints of the program are violated, and thus the program cannot be thought of as senseful (since race conditions or other hazards may occur).

The program DEPENDS on the constraints that a lock provides, via side effects; violating those results in a program that is invalid.

Depends on the side effects produced by the destructor

Changing the language from referring to a lock to a destructor is simple and obvious; if the destructor has side effects which satisfy some constraint that is required by the program to be senseful, whole, complete, and usable, then it depends on the side effects produced by the destructor. This is not exactly difficult to understand, and follows quite readily from both a legalese interpretation of the standard and a cursory layman understanding of the words and how they are used.

Now we can get into answering your questions:

Under which condition(s), if any, does this program exhibit Undefined Behavior?

Any time a dependency or requirement is not fulfilled because a destructor is not called, the behavior of any dependent code is undefined. But what does this really mean?

1.3.24 undefined behavior
behavior for which this International Standard imposes no requirements

[ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data.

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. — end note ]

Let's suppose for a moment that such behavior WAS defined.

Suppose it was explicitly illegal. This would then require any standard compiler to detect this case, to diagnose it, to deal with it in some fashion. For example, any object not explicitly deleted would have to be deleted at program exit, requiring some sort of tracking mechanism and ability to issue destructors to arbitrary types, possibly not known at compile time. This is basically a garbage collector, but given it's possibly hide pointers, it's possible to call malloc, etc etc, it would be essentially infeasible to require this.

Suppose it was explicitly allowed. This would also allow compilers to remove destructor calls, under the as-if rule, since hey, you can't depend on that behavior anyway. This would result in some nasty surprises, mostly related to memory not freeing very quickly or easily. To get around that, we'd all start using finalizers, and the problem arises yet again. Furthermore, allowing that behavior means that no library can be sure when their memory is recovered or if it ever will be, or if their locks, OS dependent resources, etc etc, will ever get returned. This pushes the requirements for clean up from the code using the resources to the code providing it, where it's basically impossible to deal with in a language like C or C++.

Suppose it had a specific behavior; what behavior would this be? Any such behavior would have to be quite involved or it wouldn't cover the large number of cases. We've already covered two, and the idea of cleaning up for any given object at program exit imposes a large overhead. For a language meant to be fast or at least minimal, this is clearly an unnecessary burden.

So instead, the behavior was labeled undefined, meaning any implementation is free to provide diagnostics, but also free to simply ignore the problem and leave it to you to figure out. But no matter what, if you depend on those constraints being satisfied but fail to call the destructor, you are getting undefined behavior. Even if the program works perfectly well, that behavior is undefined; it may throw an error message in some new version of Clang, it may delete your hard drive in some incredibly secure cryptographic OS of the far flung future, it may work until the end of time.

But it's still undefined.

Your Example

Your example does not satisfy the "depends on" clause; no constraint that is required for the program to run is unsatisfied.

  1. Constructor requires a well formed pointer to a real variable: satisfied
  2. new requires a properly allocated buffer: satisfied
  3. printf requires an accessible variable, interpretable as an integer: satisfied

No where in this program does a certain value for x or a lack of that value result in a constraint being dissatisfied; you are not invoking undefined behavior. Nothing "depends" on these side effects; if you were to add a test which functioned as a constraint that required a certain value for "x", then it would be undefined behavior.

As it stands, your example is not undefined behavior; it's merely wrong.

Finally!

Is forgetting to call a destructor any different than forgetting to call an ordinary function with the same body?

It is impossible in many cases to define an ordinary function with the same body:

  1. A destructor is a member, not an ordinary function
  2. A function cannot access private or protected values
  3. A function cannot be required to be called upon destruction
  4. A finalizer also cannot be required to be called upon destruction
  5. An ordinary function cannot restore the memory to the OS without calling the destructor

And no, calling free on an allocated object cannot restore the memory; free/malloc need not work on things allocated with new, and without calling the destructor, the private data members will not be released, resulting in a memory leak.

Furthermore, forgetting to call a function will not result in undefined behavior if your program depends on the side effects it imposes; those side effects will simply not be imposed, and your program will not satisfy those constraints, and probably not work as intended. Forgetting to call a destructor, however, results in undefined behavior, as stated on page 66:

For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, 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.

As you referenced in your original question. I don't see why you had to ask the question, given you already referenced it, but there you go.

Alice
  • 3,958
  • 2
  • 24
  • 28
  • As observed here: http://isocpp.org/std/the-standard this is not a draft in anything but name; it's the full C++11 standard with minor editorial changes. – Alice Jan 22 '14 at 15:13
  • *Your example does not satisfy the "depends on" clause; no constraint that is required for the program to run is unsatisfied.* If the program (implicitly) set a *known* seed, would the program contain UB? I mean, the program might require in the ctor of `MakeRandom` that the PRNG is in a certain state. – dyp Jan 22 '14 at 15:22
  • 1
    I'm a compiler. By the "as-if" rule, I feel well within my rights to publish an effectively identical version and proudly tell you it's the standard, given the important monetary cost of not doing this optimization. – Alice Jan 22 '14 at 15:22
  • Ok, can't fight the as-if rule. – dyp Jan 22 '14 at 15:23
  • If there is a dependency, as defined in the above text, then it would be undefined behavior. A known seed, on its own, may not be a dependency; it's only if that side effect establishes a requirement. If the ctor REQUIRED such a state, then it would be undefined behavior; if the ctor did not require such a state, it would not be undefined. – Alice Jan 22 '14 at 15:26
  • +1 Minor note: note that by "ordinary function" I meant "member function"... like imagine a function `Foo::destroy()` with exactly the same body that the destructor would have, except it is called explicitly throughout the code at the appropriate times. (You can even use `try/catch(...)` to ensure it is called in the case of exceptions.) – user541686 Jan 22 '14 at 16:02
  • Interesting, so you're thinking of "depends on X" as the *constraints* of the program depending on X, rather than the output itself. I'll have to think a bit about it, it makes sense but I certainly did not expect this interpretation... it seems a little unexpected to say a program "doesn't depend on a destructor" if the destructor affects the output of a program. – user541686 Jan 22 '14 at 16:04
  • If you meant member function, you ought to have said it; they are very different, as documented by me here: [link](http://stackoverflow.com/questions/20681758/asm-js-how-should-function-pointers-be-implemented/20835789#20835789), but the main difference between such a finalizer and a destructor is that the compiler has obligations related to destructors, while it does not related to a finalizer member function. This is the main reason they exist, after all; the compiler needs to call them at particular times. – Alice Jan 22 '14 at 16:16
  • @Mehrdad It may seem unexpected, but a dependency as necessary (but not necessarily sufficient) to provide a complete, working, and well defined program is the way the standard tends to use the word, most likely because requiring this particular part of the standard to satisfy an "as-if" level of output stability would be an unnecessary burden to the compiler, and to the speed of the program. – Alice Jan 22 '14 at 16:37
  • @Alice: There is still something bothering me about where you say *"Suppose it had a specific behavior; what behavior would this be? Any such behavior would have to be quite involved or it wouldn't cover the large number of cases."* However, it is quite trivial to rewrite every destructor into a regular method call like this: `before(); { Destructible d; foo(); } after();` translates into `before(); { Undestructible d; try { foo(); } catch (...) { d.finalize(); throw; } d.finalize(); } after();` (`finalize` has the body of the old destructor).... – user541686 Jan 26 '14 at 09:00
  • ... this behaves exactly the same, except if the `finalize` method is not invoked, the behavior is well-defined (instead of being undefined). What's so "involved" here? Why would the standard differentiate between these two cases, and in fact, why would/should the semantics of these two cases differ? – user541686 Jan 26 '14 at 09:02
  • No, that does not rewrite it into a regular method call; you have lost substantial functionality. This finalizer will not be called on scope exits, will not be called on deletes, etc etc, as outlined above. What you have created does not satisfy the concept and constraints of a destructor. The destructor is called not merely by you, but by the environment and compiler; it is impossible to fully replicate this behavior with ordinary code. – Alice Jan 26 '14 at 17:04
  • Furthermoe, you DO have undefined behavior. The difference is that rather than the standard dictating it as undefined, YOU have. When you write an RAII based class (or any similar constraint, RAII is just the easiest to dictate in a destructor), you are imposing a specific set of constraints and operations which must occur to keep those constraints satisfied. In the case of a simple RAII object with an Init and Finalizer rather than constructor/destructor, the same constraint is imposed; each Init must match a Finalizer, or all bets are off; memory could leak, files corrupted, etc. – Alice Jan 26 '14 at 17:07
  • When you fall to clean up after yourself, you are violating a constraint you imposed on the system, and that means your clean abstract interface breaks down. Those constraints are there to make the messy or variable internals easier to manage. So I would argue that leaving the finalizer off DOES result in undefined behavior; no part of the system will correct your mistake, and it will be impossible to track, which makes it unpredictable and unrecoverable, with no requirements for documentation or diagnostics. The only difference is in the case of the Finalizer, YOU are the standard writer. – Alice Jan 26 '14 at 17:12
  • @Alice: You didn't @-notify me so I didn't see this until now. I think you missed my point. I was saying that it is 100% possible to rewrite a program using destructors into an equivalent program without destructors (but with manual call of finalizers), although it may require duplication of code and may be very inconvenient in some cases. In the latter case, if the finalization is omitted, the program will *not* have undefined behavior; the behavior will be quite well-defined. Therefore the question is why destructors should be any different. – user541686 Jan 27 '14 at 02:46
  • Not 100% convinced it's correct -- so I'll avoid accepting an answer for now -- but I'll give you the bounty since your answer is decently rigorous and consistent. Enjoy! – user541686 Jan 27 '14 at 02:54
  • @Mehrdad Sorry, I ran out of letters a lot and forgot. And I fully disagree; you cannot rewrite it to an equivalent program, but also, it would still be not well defined; the standard would say it is well defined, but your own internal constraints would be violated, and thus it would have undefined behavior. Let me flip the question on you: why is the standard's constraints any different than your own internal ones? – Alice Jan 27 '14 at 13:24
  • @Alice: I don't believe anything is violated? The standard's constraints are simply *looser* than mine; mine gives well-defined behavior for the situation in which the finalizer isn't called, but the standard doesn't. I believe the semantics are otherwise the same. Why do you think it's not possible to rewrite the program? – user541686 Jan 27 '14 at 18:51
  • Firstly, consider "The visible sequence of side effects depends on the “happens before” relation, which depends on the values observed by loads of atomics, which we are restricting here" - clearly depends on doesn't mean one is required for the other to exist or happen - the dependency exists if one is affected by the other. Given the rand call in the destructor would - if executed - affect later rand calls, there's a dependency - that makes the behaviour undefined, after which you can't apply the as-if rule because you can't reason about what the observable behaviour should be. – Tony Delroy Jun 10 '14 at 14:32
  • +1 For a very thoughtful answer. However a question remains: why does the standard have to mention UB here, or indeed include the final phrase "and any program that depends..." at all? It being stated that the destructor shall not be called, clearly anything that depends on the destructor _being_ called is living under a false hypothesis, including any correctness proof of a program that has such dependence. In fact the UB seems to mandate calling the destructor anyway (contradicting the previous): either the program is independent of any side effects of that call, or else it has UB! – Marc van Leeuwen Jun 12 '14 at 10:45
  • Suppose a program does nothing but send "Wow" to stderr, and create and abandon an object whose destructor sends "Hey" to stdout. Suppose further that the specification for the program says that it will be deemed to have run correctly if it outputs "Wow" to stderr and either "Hey" or nothing to stdout. Would the standard allow the program to do anything other than the above? Suppose instead that the requirements for the program specified that it must send "Wow" to stderr and "Hey" to stdout. In that case, would the standard forbid the program from launching nuclear missiles? – supercat Apr 17 '15 at 21:19
  • @MarcvanLeeuwen: If one wanted to be tricky, one could say that since the compiler couldn't spontaneously create UB if the destructor was called, a compiler could spontaneously create UB if the destructor wasn't called; if the UB generated by the compiler ended up yielding a "correct execution", it would be legitimate, and if it didn't the prevention of incorrect execution by blocking UB would be a side-effect upon which correct execution depended (thus meaning that the missing destructor would justify UB). – supercat Apr 17 '15 at 21:37
2

Whether a program "depends on the side effects produced by a destructor" hinges on the definition of "observable behavior".

To quote the standard (section 1.9.8, Program execution, bold face is added):

The least requirements on a conforming implementation are:

  • Access to volatile objects are evaluated strictly according to the rules of the abstract machine.
  • At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.
  • The input and output dynamics of interactive devices shall take place in such a fashion that prompting output is actually delivered before a program waits for input. What constitutes an interactive device is implementation-defined.

These collectively are referred to as the observable behavior of the program. [ Note: More stringent correspondences between abstract and actual semantics may be defined by each implementation. ]

As for your other question:

Is forgetting to call a destructor any different than forgetting to call an ordinary function with the same body?

Yes! Forgetting an "equivalent" call to a function leads to well defined behavior (whatever it was supposed to make happen doesn't happen), but it's quite different for a destructor. In essence, the the standard is saying that if you engineer your program such that an observable destructor is "forgotten," then you're no longer writing C++, and your program result is completely undefined.

Edit: Oh right, the last question:

Under which condition(s), if any, does this program exhibit Undefined Behavior?

I believe printf qualifies as writing to a file, and is therefore observable. Of course rand() is not actually random, but is completely deterministic for any given seed, so the program as written does exhibit undefined behavior (that said, I would be really surprised if it didn't operate exactly as written, it just doesn't have to).

Profram Files
  • 376
  • 2
  • 6
  • +1, decent answer. Thanks for directly answering the second one. And also nice point about the last one, I totally forgot about `srand`. – user541686 Jan 22 '14 at 04:33
2

First of all, we need to define undefined behavior, which according to the C FAQ would be when:

Anything at all can happen; the Standard imposes no requirements. The program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended.

Which, in other words, means that the programmer cannot predict what would happen once the program is executed. This doesn't mean that the program or OS would crash, it simple means that the program future state would only be know once that it is executed.

So, explained in math notation, if a program is reduced to a function F which makes a transformation from an initial state Is into a final state Fs, given certain initial conditions Ic


F(Is,Ic) -> Fs


And if you evaluate the function (execute the program) n times, given that n-> ∞


F(Is,Ic) -> Fs1, F(Is,Ic) -> Fs2, ..., F(Is,Ic) -> Fsn, n-> ∞


Then:

  • A defined behavior would be given by all the resulting states being the same: Fs1 = Fs2 = ... = Fsn, given that n-> ∞
  • An undefined behavior would be given by the possibility of obtaining different finished states among different executions. Fs1 ≠ Fs2 ≠ ... ≠ Fsn, given that n-> ∞

Notice how I highlight possibility, because undefined behavior is exactly that. There exists a possibility that the program executes as desired, but nothing guarantees that it would do so, or that it wouldn't do it.

Hence, answering your answer:

Is forgetting to call a destructor any different than forgetting to call an ordinary function with the same body?

Given that a destructor is a function that could be called even when you don't explicitly call it, forgetting to call a destructor IS different from forgetting to call an ordinary function, and doing so COULD lead to undefined behavior.

The justification is given by the fact that, when you forget to call an ordinary function you are SURE, ahead of time, that that function won't be called at any point in your program, even when you run your program an infinite number of times.

However, when you forget to call a destructor, and you call your program an infinite number of times, and as is exemplified by this post: https://stackoverflow.com/questions/3179494/under-what-circumstances-are-c-destructors-not-going-to-be-called under certain circumstances, C++ destructors are not called, it means that you can't assure beforehand when the destructor would be called, nor when it wouldn't be. This uncertainty means that you can't assure the same final state, thus leading to UB.

So answering your second question:

Under which condition(s), if any, does this program exhibit Undefined Behavior?

The circumstances would be given by the circumstances when the C++ destructors are not called, given on the link that I referenced.

Doverman
  • 21
  • 2
  • What you describe isn't true since C/C++ officially introduced threads. Which pretty much means that since they did, there is no C/C++ semantics. – curiousguy Sep 21 '19 at 14:28
1

In the comments you've left a simple question that made me rethink what I said. I've removed the old answer because even if it had some value, it was far from the point.

So you're saying my code is well-defined, since it "doesn't depend on that even if I print it"? No undefined behavior here?

Let me say again that I don't precisely remember the definition of placement new operator and deallocation rules. Actually, I've not even read the newest C++ standard in full. But if the text you quoted is from there, then you are hitting the UB.

Not due to Rand or Print. Or anything we "see".

Any UB that occurs here is because your code assumes that you can safely "overwrite" an old 'object' without destroying the previous instance that was sitting at that place. The core sideeffect of a destructor is not "freeing handles/resources" (which you do manually in your code!) but leaving the space "ready for being reclaimed/reused".

You have assumed that the usage of the memory chunks and lifetimes of objects are not well-tracked. I'm pretty sure that the C++ standard does not define that they are untracked.

For example, imagine that you have the same code as provided, but that this struct/class has a vtable. Imagine that you are using hyper-picky compiler which has tons of debugchecks that manages the vtable with extra care and allocates some extra bitflag and that injects code into base constructors and destructors that flips that flag to help to trace errors. On such compiler, this code would crash on the line of new (r) MakeRandom since first object's lifetime has not been terminated. And I'm pretty sure that such picky compiler would still be fully C++ compliant, just as your compiler surely is too.

It's an UB. It's only that most compilers really don't do such checks.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • Hmm, but the standard says forgetting to call the destructor is only UB if the "program depends on the side effects produced by the destructor". If I'm reading that correctly, that means I'm perfectly fine to avoid calling it and re-construct an object on top of an old one when I simply don't "depend" on the destructor getting called. So my question here is what it means to "depend" on the destructor running (e.g. does the given program "depend" on it?). – user541686 Jan 14 '14 at 11:13
  • IMHO, destructors release the memory chunks for reuse. The normal new() operator allocates and takes use of some chunk. On the other hand, the new(r) placement operator is given a chunk and assumes that the chunk is ready. Forgetting to call the destructor between them results in the STD saying "destructor shall not be implicitely called". But your very next line, the `new(r)`, assumes/depends that the `*r` block is unused! So it assumes/depends that someone had already made the memory chunk ready for reuse == you are relying on the destructor, which was not called, hence UB. – quetzalcoatl Jan 14 '14 at 11:21
  • 1
    +1 because it's at least a consistent answer, however I'm not sure totally if it's correct... – user541686 Jan 14 '14 at 11:23
  • Yeah, it makes sense if it's true, it's just that I'm not sure if it's actually true that you can't construct an object on top of another object with a nontrivial destructor. Is there actually such a restriction on `new` that says the destructor of a preexisting object must have been called? – user541686 Jan 14 '14 at 11:25
  • No, I dont think it is plainly stated anywhere with this words. I'd search for "lifetimes". I'm pretty sure that the STD says that forcibly calling a constructor on an already constructed object is an UB too. And it would fit both "common sense" and the above reasinong. But I'm just "pretty sure", I have not checked it. – quetzalcoatl Jan 14 '14 at 11:31
  • What Mehrdad means is that you could have malloc()'ed the space, placement new'ed there and then free()'d it without calling the destructor. The question is about the side effects of the dctor, not placement new. – Nikos C. Jan 14 '14 at 11:45
  • Yeah. If I hadn't done the placement-new (and the subsequent destruction of the second object) then would it have been well-defined? – user541686 Jan 14 '14 at 11:48
  • Without placement-new and without the 'manual destruction', it's just a memory leak which actually seems perfectly legal ;) in terms of UB: http://stackoverflow.com/questions/1978709/are-memory-leaks-undefined-behavior-class-problem-in-c. Leak is a well-defined flaw of your program. I was saying that any UBs that may occur in the presented case, IF any at all, are due to trying to re-construct a still-living instance. That's why you must check what STD defines or un-defines about lifetimes and new(r) op. – quetzalcoatl Jan 14 '14 at 12:08
  • 1
    @Mehrdad *"when I simply don't "depend" on the destructor getting called"* -- *You* are not *the program* ;) i.e. it's not clear that only the explicitly written code must not depend on the side effects. – dyp Jan 14 '14 at 14:02
  • +1. I can't believe this was the first answer to mention placement new. – Mike Jan 24 '14 at 21:06
1

My reading of this portion of the standard is:

  • You are allowed to reuse the storage for an object that has a non-trivial destructor without calling that destructor
  • If you do, the compiler is not allowed to call the destructor for you
  • If your program has logic that depends on the destructor being called, your program might break.

Side effects here are simply changes in program state that result from calling the destructor. They will be things like updating reference counts, releasing locks, closing handles, stuff like that.

'Depends on the side effects' means that another part of the program expects the reference count to be maintained correctly, locks to be released, handles closed and so on. If you make a practice of not calling destructors, you need to make sure your program logic does not depend on them having been called.

Although 'forgetting' is not really relevant, the answer is no, destructors are just functions. The key difference is that under some circumstances they get called by the compiler ('implicitly') and this section of the standard defines a situation in which they will not.

Your example does not really 'depend on the side effects'. It obviously calls the random function exactly 3 times and prints whatever value it calculates. You could change it so:

  • The struct maintains a reference count (ctor +1, dtor -1)
  • A factory function reuses objects and randomly calls the destructor or not
  • A client function 'depends on' the reference count being maintained correctly, by expecting it to be zero.

Obviously, with this dependency the program would exhibit 'undefined behaviour' with respect to the reference count.

Please note that 'undefined behaviour' does not have to be bad behaviour. It simply means 'behavior for which this International Standard imposes no requirements'.

I really think there is a danger of overthinking what is fundamentally quite a simple concept. I can't quote any authority beyond the words that are here and the standard itself, which I find quite clear (but by all means tell me if I'm missing something).

david.pfx
  • 10,520
  • 3
  • 30
  • 63
  • Your answer seems to be saying the destructor is just like any other function. If you don't call it, the code won't execute, it's simple as that. I would love to believe this is true, but the standard seems to imply this is false and they're in fact special in some way -- i.e. that not calling a destructor is a bit different from not calling a regular function, such that not calling a function you depend on results in *well-defined* (but possibly unwanted) behavior, whereas not calling a destructor you "depend on" results in *undefined* behavior. So are destructors indeed special here or no? – user541686 Jan 21 '14 at 15:38
  • Destructors are not like ordinary functions in that they are subject to a number of restrictions, and they can be called implicitly (by the compiler) in several situations: see S12.4, which gives 6. We are talking about a very specific provision (S3.8) which says that reusing storage is not one of them, so if calling the destructor matters then you better take care of it. Also see minor edit. – david.pfx Jan 22 '14 at 14:30
1

This exact issue with the wording is the subject of editorial pull request against the draft C++ standard [basic.life] Remove description of impossible UB which seeks to strike this wording from the draft standard:

A destructor that is not called cannot produce side effects, therefore it is impossible to depend on those side effects.

After much discussion it seemed to lean towards that direction:

Editorial meeting: The standard rules cannot depend on the programmer's intent. Pass to CWG with the intent of applying as-is.

but it needs to be reviewed by the Core Working Group (CWG) first and is therefore not an editorial change. I believe this means it will eventually show up as a defect report.

So in conclusion this looks like an open issue as to whether that wording has any meaning at all but it will be reviewied by CWG eventually.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
0

I have not read everyone else's input, but I have a simple explanation. In the quote

however, if there is no explicit call to the destructor or if a delete-expression 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 meaning is very different depending on how you parse it. This meaning is what I hear people talking about.

however, { if there is no explicit call to the destructor or if a delete-expression 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.

But I think this meaning makes more sense

however, if there is no explicit call to the destructor or { if a delete-expression 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 } .

which basically says C++ does not have a garbage collector and if you assume it does have GC your program will not work as you expect.

brian beuning
  • 2,836
  • 18
  • 22
0

The standard is required to speak in such terms as observable behavior and side effects because, although many people often forget this, c++ is not just used for PC software.

Consider the example in your comment to Gene's answer:

class S { 
    unsigned char x; 
    public: ~S() { 
        ++x; 
    } 
};

the destructor here is clearly modifying an object -- hence that's a "side effect" with the given definition -- yet I'm pretty sure no program could "depend" on this side effect in any reasonable sense of the term. What am I missing?

you are missing the embedded world for example. Consider a bare metal c++ program running on a small processor with special function register access to a uart:

new (address_of_uart_tx_special_function_register) S;

here calling the destructor clearly has observable side effects. If we don't call it, the UART transmits one byte less.

Therefore whether side effects are observable also depends on what the hardware is doing with the writes to certain memory locations.

It may also be noteworthy that even if the body of a destructor is empty it could still have side effects if any of the classes member variables have destructors with side effects.

I don't see anything forbidding the compiler from doing other bookkeeping (maybe with regard to exceptions and stack unwinding). Even if no compiler currently does and no compiler ever will from a language lawyer point of view you still have to consider it UB unless you know that the compiler doesn't create side effects.

odinthenerd
  • 5,422
  • 1
  • 32
  • 61
  • Correct me if I'm wrong but I believe the compiler is indeed allowed to optimize this out, making the UART transfer fewer bytes. If that is not what you want then you should have used `volatile` somewhere. – user541686 Jan 24 '14 at 21:52
  • yes the compiler is allowed to optimise it away without the volatile, but that is beside the point and may or may not happen (so its also UB). Forgetting to call a destructor is still UB as long as you cannot prove that its effects are not observable which is impossible in most cases and should include a code review of the compiler. – odinthenerd Jan 26 '14 at 22:49
  • I don't see why the fact that you can't prove something is unobservable matters here. If the compiler can't prove something then it has to take a conservative route. That means if it can't prove the effects are observable then it has to assume they are unobservable and compile the program accordingly. – user541686 Jan 27 '14 at 02:52
  • The wording of the standard is such that the compiler is allowed to rely on destructors being called if the destructor has side effects. It is the role of the programmer to prove that the efects are not observable and only when the programmer can prove that then and only then is behavior defined. – odinthenerd Jan 27 '14 at 10:04
  • No you're wrong. It is the role of the **compiler** to produce the correct behavior when the effects are unobservable, **irrespective** of whether the programmer can prove the observability. This does **not** require the compiler to prove the effects are (or aren't) observable. If the compiler is unable to prove the effects are observable, it must conservatively assume they are unobservable; otherwise it will jeopardize the correctness of the program. – user541686 Jan 27 '14 at 10:28
  • Where exactly does it say that the compiler must assume unobservability? – odinthenerd Jan 27 '14 at 11:47
  • It's common sense. The compiler can't assume observability without proving it, otherwise it would fail to keep the program correct when the changes would be unobservable. So if it can't prove it, it must stay conservative and err on the side of caution. That's my whole point. – user541686 Jan 27 '14 at 18:52
-1

Say you have a class that acquires a lock in its constructor and then releases the lock in its destructor. Releasing the lock is a side affect of calling the destructor.

Now, it's your job to ensure that the destructor is called. Typically this is done by calling delete, but you can also call it directly, and this is usually done if you've allocated an object using placement new.

In your example you've allocate 2 MakeRandom instances, but only called the destructor on one of them. If it were were managing some resource (like a file ) then you'd have a resource leak.

So, to answer your question, yes, forgetting to call a destructor is different to forgetting to call an ordinary function. A destructor is the inverse of the constructor. You're required to call the constructor, and so you're required to call the destructor in order to "unwind" anything done by the destructor. This isn't the case with an "ordinary" function.

Sean
  • 60,939
  • 11
  • 97
  • 136
  • Sorry but you're not answering the question. If the entire subsequent *behavior* is undefined then a mere resource leak would be the least of my worries. The question is, which of those is the case? – user541686 Jan 14 '14 at 10:09
  • @Mehrdad - I've added some more detail. – Sean Jan 14 '14 at 10:21
  • Thanks. I disagree with your last statement. What if I do `new int;`, thereby forgetting to call the destructor? Does the rest of my program exhibit undefined behavior? I'm almost certain the answer is no, so unless you're claiming it's "yes", that's wrong... – user541686 Jan 14 '14 at 10:25
  • @Mehrdad - but `int` isn't a class with a non-trivial destructor (see the section of the standard you posted in the question). – Sean Jan 14 '14 at 10:27
  • Er, that wasn't my point. Pretend I said `new MakeRandom(&x);` instead, but didn't do anything else afterward. Is that undefined behavior? I'm pretty sure the answer is no -- nothing depends on this statement because nothing comes after it, so it's not undefined behavior. – user541686 Jan 14 '14 at 10:28
  • @Mehrdad - if you depend on the side effects of the destructor to `MakeRandom` then yes, it's undefined – Sean Jan 14 '14 at 10:32
  • @Mehrdad - I've altered the example in my answer to use locks. Here, by not calling the destructor you've not released the lock, which is a side effect that your program depends upon to work correctly. – Sean Jan 14 '14 at 10:35
  • Uh, now we're back to square one. What does "depend on" mean? If a value is random either way, can you "depend" on it? The entire point of my question has been to get that clarified so we haven't gotten anywhere. – user541686 Jan 14 '14 at 10:35
  • The examples in your answer are obvious (it's pretty clear what it means when a lock doesn't get released), so it's kinda avoiding the question. I'm asking about the non-obvious situations like the one in my question, where you can't possibly differentiate between the destructor running and not running. (i.e. if the destructor was empty, you couldn't possibly tell that it was missing -- so is the program "depending" on it then?) – user541686 Jan 14 '14 at 10:36
  • @Mehrdad - you're over-complicating this. The key bit is the bit you highlighted `any program that depends on the side effects produced by the destructor has undefined behavior`. Your example depends on the behaviour in the destructor, so it is undefined behaviour. – Sean Jan 14 '14 at 10:41
  • Depends in what way? How would it have behaved differently if the destructor wasn't there? – user541686 Jan 14 '14 at 10:42
  • @Mehrdad - sorry, my mistake. Since you only print `x` then there's potentially no dependency. It's your job to know if you depend on the destructor. For example, maybe you really do depend on the `xor` operation altering `x`. If you do then it's a dependency, if not then there isn't a dependency. If you're not sure then just ensure the destructor is called the correct number of times. That was the point of the locking example in my answer. – Sean Jan 14 '14 at 10:50
  • You keep on saying *"if you depend on it.."* but I'm asking *what does that mean*? Can you give a definition? For example, it might mean "affects observable behavior", defined via the I/O semantics in the standard (e.g. I think the standard says using `volatile` variables or calling I/O functions like `printf` are considered observable behavior). Or it might mean "its value is copied onto some other variable", regardless of whether it's observable. Or something else, I don't know. You keep on repeating that phrase but it doesn't get us anywhere because the question is what that phrase *means*. – user541686 Jan 14 '14 at 10:57
  • @Mehrdad - This isn't difficult, so I'm surprised you're having so much trouble with it. If you rely on the behaviour of a destructor elsewhere in your program then you've got UB if you don't call it. All the examples you give (volatile, IO, etc) which highlights the point that it's very domain specific. – Sean Jan 14 '14 at 11:05
  • Sigh. C++ is defined in terms of an abstract machine, and two programs are considered equivalent if they have the same I/O behavior (volatile is considered I/O) relative to that abstract machine, and the compiler is free to substitute any for the other. The problem here is that the I/O certainly uses the variable, but as far as I can tell it's impossible to tell if the destructor ran or not merely from observing the output. So I can't tell if the program "depends on"/"relies on" the destructor or not in this abstract machine. If it's not clear then I'll just leave this here as-is, I'm done. – user541686 Jan 14 '14 at 11:08
-1

It basically means that when you define your own destructor for a class, it is no longer called automatically upon leaving scope. The object will still be out of scope if you try to use it, but the memory will still be used up in the stack and anything in your non-default destructor will not happen. If you want the count of objects to decrease whenever you call your destructor, for example, it will not happen.