Is a C++ destructor guaranteed not to be called until the end of the block?
Asked Answered
E

8

39

In the C++ code below, am I guaranteed that the ~obj() destructor will be called after the // More code executes? Or is the compiler allowed to destruct the obj object earlier if it detects that it's not used?

{
  SomeObject obj;
  ... // More code
}

I'd like to use this technique to save me having to remember to reset a flag at the end of the block, but I need the flag to remain set for the whole block.

Entrepreneur answered 18/1, 2010 at 16:41 Comment(3)
It's very neat, because your destructor will be called in the event of an exception being thrown as well.Noe
Thanks for the answers - I thought it was the case, but was worried I'd missed a fine detail.Entrepreneur
+1: Very good question: I also use RAII (the offical term for this technique) everywhere but never asked myself if it is guaranteed to work!Peg
C
48

You are OK with this - it's a very commonly used pattern in C++ programming. From the C++ Standard section 12.4/10, referring to when a destructor is called:

for a constructed object with automatic storage duration when the block in which the object is created exits

Corduroy answered 18/1, 2010 at 16:43 Comment(2)
+1 Also, 3.7.2/3 reinforces: "If a named automatic object has initialization or a destructor with side effects, it shall not be destroyed before the end of its block, nor shall it be eliminated as an optimization even if it appears to be unused"Leningrad
Where a "block" is the containing syntactic scope, right?Hinch
H
27

Actually...

C++ has something called the "as if" principle. All the guarentees made referenced in all of these answers only refer to the observable behavior. The compiler is allowed to ellude, reorder, add, etc.. any function call, as long as the observable behavior is as if it had executed as originally written. This also applies to destructors.

So, technically, your observation is correct: the compiler is allowed to destruct the object earlier, if it detects it is not used, and there are no observable side effects from the destructor or any function it calls. But, you are guarenteed to not be able to tell this is happening outside of a debugger, because if you were able to tell, the compiler would no longer be able to do it.

It's more likely the compiler uses this power to do something useful like completely ellude a trivial destructor rather than actually reorder destructor calls, however.

Edit: Someone wanted a reference... 1.9/5, along with footnote 4 of the C++0x draft standard (this isn't a new rule, I just don't have the C++03 standard handy. It's also present in the C standard, AFAIK)

1.9/5:

A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible execution sequences of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution sequence 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).

Footnote 4:

This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. For instance, an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced.

My reading (and what I thought was the general understanding) was that this is what enables the compiler free hand to do whatever it wants (ie, enables optimizations), as long as the observable behavior is that of the original written source - including moving around destructors, not destructing objects at all, inventing destructors, etc.

Hooten answered 18/1, 2010 at 18:1 Comment(9)
No, the compiler is not allowed to do this. If you think it is, please quote from the C++ Standard, supporting your view. And see litb's comment on my answer.Corduroy
Well, he's right, and litb's comment supports it. That quote says if the destructor has side effects. Point is that if it makes no difference, the compiler may reorder things. But of course it's an academic point, since this may open happen in cases where it makes no difference, so it's not worth worrying about.Alphonse
@Jalf "if a named automatic object has initialization ... it shall not be destroyed before the end of its block, nor shall it be eliminated as an optimization even if it appears to be unused" - the destructor with side effects is optional.Corduroy
Yes; I bring it up because the original question alluded to being curious about this when he asked "Or is the compiler allowed to destruct the obj object earlier if it detects that it's not used"Hooten
@Neil "an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed"Hooten
Side effects are different from observable behavior. For instance i++; has a side effect no matter whether i is volatile or not. But if i is not volatile, it has no observable behavior. But i think i agree that it doesn't matter: If the side effects are not observed outside of the constructor of it, the implementation can just optimize the variable out, i think. It's just a wording issue, but i think the intent is clear.Leningrad
@Neil, obviously the binding is "If a named automatic object has ((initialization or a destructor) with side effects)". Otherwise, you would be likely to find it written "If a named automatic object has a destructor with side effects or initialization". It does not say "constructor", because you can have something like int a = i++;, in which case the initialization of a has a side effect.Leningrad
@Johannes Where are you getting these differences between side effects and observable behaviour from, re the standard?Corduroy
@Neil, my notebook fan is defective so i can't access my box now :( So i dont have access to c++03. I took c++0x now, and you find these terms explained for c++0x at 1.9/8 and 1.9/12 in n3000. A similar difference exists in C++03.Leningrad
N
19

The destructor will not be called until the object goes out of scope.

The C++ faq lite has a good section on dtors

Nitroso answered 18/1, 2010 at 16:42 Comment(1)
+1, C++ destructors are deterministic which is why you have this guarantee.Vazquez
P
9

Destruction in C++ is deterministic - meaning that the compiler is not free to move that code around. (Of course optimization might inline the destructor, determine that the destructor code does not interact with // More code and do some instruction reordering, but that's another issue)

If you couldn't depend on the destructors being called when they are supposed to be called, you couldn't use RAII to grab locks (or just about any other RAII construct for that matter):

{
    LockClass lock(lockData);
    // More code
} // Lock automatically released.

Also, you can depend on destructors running in reverse order of how the objects were constructed.

Parashah answered 18/1, 2010 at 16:48 Comment(3)
It may re-order instructions between two sequence points. It may not move them between sequence points. So you are uranteed that the objects will be destroyed correctly in the reverse order of creation.Newark
@Martin, the compiler has such limitations. The processor pipeline does not. But if you're using the constructor/destructor to call locking APIs, you can depend on those APIs to have the right barriers in place.Parashah
The C++ standard is a contract between me and the compiler: if I write standard C++, the compiler is supposed to make it run in a standards-conformant way. If the compiler emits object code that runs incorrectly on a particular processor, it isn't conforming on that processor. It's the compiler's job to make sure a processor pipeline runs my code correctly, not mine.Milker
L
5

Yes, it is guaranteed.

The lifetime of an object with automatic storage duration ends at the end of its potential scope and not before. For such an object the potential scope begins at the point of declaration and ends at the end of the block in which it is declared. This is the moment when the destructor will be called.

Note, that very pedantically speaking, even for an automatic object it is not correct to say it is destroyed when it "goes out of scope" (as opposed to "goes out of its potential scope"). The object can go out of scope and back into scope many times (if even more local objects with the same name are declared within the block), and going out of scope in such fashion does not cause the destruction of the object. It is the "very final end" of its scope that kills the automatic object, which is defined as the end of its potential scope as described above.

In fact, the language standard does not even rely on the notion of scope to describe the lifetime of automatic objects (no need to deal with all these terminological intricacies). It just says that the object is destroyed at the exit of the block in which it is defined :)

Limpopo answered 18/1, 2010 at 16:51 Comment(1)
+1 for the difference of potential scope vs scope. This difference of potential scope and scope is what scared me recently. The Standard in fact says "A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed...". So it in fact says this is ill-formed: { string a; { int a; goto foo; } foo: ; }. Of course that's not the intent :)Leningrad
D
4

All of the answers here address what happens with named objects, but for completeness, you probably should know the rule for temporary/anonymous objects too. (e.g. f(SomeObjectConstructor() or f(someFunctionThatReturnsAnObject()))

Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. (12.2/3 from the ISO C++98 standard)

which basically means that temporarily generated objects persist until the next statement. Two exceptions are for temporaries generated as part of an object's initialization list (in which case the temporary is destroyed only after the object is fully constructed) and if a reference is made to a temporary (e.g. const Foo& ref = someFunctionThatReturnsAnobject()) (which case the lifetime of the object is the lifetime of the reference).

Dekker answered 18/1, 2010 at 19:34 Comment(2)
IIRC, the reference in your last sentence has to be a const reference.Milker
@David: Whoops, that was careless of me. Thanks for pointing that out. Fixed now.Dekker
H
3

Yes, the C++ standard has very specific requirements about when objects are destroyed (in §12.4/10), and in this case it must not be destroyed until after all the other code in the block has finished executing.

Housekeeping answered 18/1, 2010 at 16:48 Comment(0)
M
1

A typical example of this, just as your question is the boost::scoped_ptr (or similar std::auto_ptr) :

{
    boost::scoped_ptr< MyClass > pMyClass( new MyClass );

    // code using pMyClass here

} // destruction of MyClass and memory freed
Maritime answered 18/1, 2010 at 16:48 Comment(1)
This is a slightly different case - the code after the object creation uses the object. My specific question was what would happen if I didn't use the object at all after creation - so I'm only creating the object so that its destructor will run at the end of the block.Entrepreneur

© 2022 - 2024 — McMap. All rights reserved.