How to make destructors be called on going out of scope? - d

With GC on, is it possible for destructors to be called right after going out of scope.
Is it possible for destructors to be called on going out of scope for all objects [of any type]?
Why aren't destructors called when going out of scope anyway?
In this post "scope on a local variable" is said to be "unsafe". Why is it considered unsafe?
And the rationale for deprecating the feature is
scope as a type constraint was a quirk in the language without a compelling use case.
No compelling use case? Like placing objects on the stack(this feature does that, right?) isn't faster than on the heap.

Generally, the D GC does not call destructors when returning from a function. The GC is triggered when an allocation occurs, and when GC.collect is called.
D has scoped, which wraps a non-RAII type in a struct, which has RAII behavior. This way, the struct's destructor can take care of cleaning up the memory. Note that, while this generally works, there are some corner cases and things to be aware of that a GC will handle automatically. This allows destructors to be called on any object when leaving a scope.
Destructors are not called on all objects when they leave scope because there may be other references to the objects. Consider this code:
int* global;
void func() {
int* p = new int;
global = p;
}
If the int pointed to by p was destructed when func() returned, then global would point to destructed memory.
The article you link is almost ten years old, and D has changed a bit in the meantime. scope now has better semantics, which meaningfully limit what you can do with variables marked such:
return ref
return scope
ref return scope
scope local variables may still be assigned to globals, which seems like an oversight. I'll file a bug if I can't find an existing one.

With GC on, is it possible for destructors to be called right after going out of scope.
Is it possible for destructors to be called on going out of scope for all objects [of any type]?
Destructors are called for any object on the stack, this applies to structs and classes allocated as scope Foo f = new Foo().
In this post "scope on a local variable" is said to be "unsafe". Why is it considered unsafe?
Because one could escape a reference to the stack allocated instance which might outlive the current function call. (But DIP 25 / 1000 detect's most of these issues)
And the rationale for deprecating the feature is [...]
No compelling use case? Like placing objects on the stack(this feature does that, right?) isn't faster than on the heap.
Only scope attached to the type declaration is deprecated.
But as a general recommendation, use structs if your object requires deterministic destruction (e.g. File handles).

Related

What is the relation between C storage-class and C++ destructor

I am very new to C/C++ programming.
Storage class in C signifies the visibility and life cycle of a variable.
In C++, Constructor and Destructor are used to initialize & release-resources the object occupied.
Yes, constructor helps reducing much of repetitive code but destructors are used to release and/or free resources (once an object goes out of scope).
Are these concepts coupled in some way in their implementation?
For C Storage classes see: https://stackoverflow.com/a/2661411/8740349
Let's not talk about implementions, as each compiler works differently, but if you ask about C++ spec, most keywords mean the same.
Except that:
register keyword was removed since C++17 (after being deprecated in C++11), without any alternative.
auto means to auto-detect type, like:
auto myVariable = myFunction("blablabla (how old are you, in Chinese)");
Is destructor related to storage-class?
No, C++ Class and/or Struct is destructed before it's storage is deallocated.
As mentioned in comments: Handling deallocation of storage isn't the subject of the destructor but the subject of the respective "deallocator" (provided by the compiler e.g. for global and local variables or the delete for memory allocated with new).

Is using placement new with variable on the stack is correct?

Let's take a look to this code:
A a(123);
new(&a) A(124);
Test says that in this case when program is shutting down destructor ~A() will called once. So if in A we has some pointers as fields we will get memory leak.
A a(123);
a.~A();
new(&a) A(124);
Here all will be correct. But according to standard using object after destructor calling is undefined behaviour(though mostly compilers provide behaviour without some troubles).
Can I take an address of object which destructor has been called?
Is calling placement new on stack variable is correct operation?
Can I take an address of object which destructor has been called?
[edited:]
Such an address is valid so if you have a pointer to it, it is valid.
Can you take its address after the fact, I am not fully sure.
basic.life.6 […] After the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object was located may be used but only in limited ways. […] Such a pointer refers to allocated storage, and using the pointer as if the pointer were of type void* is well-defined.
Check out the full text for the all the restrictions, but use in placement-new is allowed.
As for the comments, I would argue both your samples are correct within the scope you showed.
A a(123);
new(&a) A(124);
I would argue this, given what we know in the sample, is correct.
Ending the lifetime of an object by re-using its storage is valid as per basic.life.5. Only condition is the program does not depend on side effects produced by the destructor - to be on the safe side I would only do that on trivially destructible types. Otherwise you need an explicit destructor call, like you did there:
A a(123);
a.~A();
new(&a) A(124);
I do not see any rule preventing that. The standard even explicitly mentions when such a construct is invalid:
If a program:
ends the lifetime of an object of type T with static, thread, or
automatic storage duration
and another object of the original type does not occupy that same
storage location when the implicit destructor call takes place,
the behavior of the program is undefined.
(formatting as bullet points mine)
Though not a definite proof, this passage suggests that in other cases, the behavior is defined. I cannot think of another rule that his would violate.
(Do note I use C++20 version of the standard)
Yes, this usage is valid. The storage for an object is independent from the object's lifetime.
By calling the destructor, you are ending the object's lifetime, but that does not mean that the storage is released.
According to the standard, that storage may be reused or released. And what you do is reusing it.
This exact case is well-defined in the standard in basic.life#8
Keep in mind that, because 'a' is a variable with automatic storage, at the end of the scope the destructor for that variable's type will be invoked.

Is double-construction undefined behaviour?

In our codebase, a pool of memory chunks is used and upon 'allocation' the object gets constructed using a "placement new". I'm missing the destructor call though, finding it odd to allow "double construction", and wonder if it is undefined behaviour to call the constructor another time on the same object.
In C++11 3.8.4 [basic.life] it reads
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 (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.
Does this mean that missing out the destructor call is actually ok, as long as we talk about types which have destructors without side-effects?
Edit: Context is: embedded SW without heap, own container implementations doing placement-new on c-array-elements or byte-arrays.
You can double construct without UB. The new object is a completely different one, and following pointers/references to the old one is UB.
It is really easy to trip over UB when doing placement new, let alone when double constructing.
Not calling a destructor just means the object is not cleaned up. If the destructor is trivial, this is not going to have much risk. Constructing an object over another object end an objects lifetime, as does calling the destructor.
If the object is in automatic storage with the proper type, constructing a different object over it and exiting the scope (causing the no longer existing original object to be destroyed) is usually UB (you can avoid UB here with a trivial destructor of the original object; this is why byte-buffers can have placement new done in them).
This answer already answers the question well.
Although, I would add that using placement new in production code is usually a bad practice, error prone and/or a mistake. Many excellent alternatives exist (including some libraries). So you should look to simpler alternatives (could be as simpel as using a std::vector instead - calling reserve() if absolutely needed!) - also sometimes the use and introduction of placement new in codebase in the first place is oftentime misguided, legacy or sometimes a case of premature optimization.
Albeit it's hard to give concrete advice - one method could be just remove placement new or replace it with some kind of standard container - and then check if any problems arises (usually performance).
In my experience just removing the use of placement new in legacy code leads to no issues since the performance problem that it (maybe) used to fix has become largely obsolete by a wide range of 'system' optimizations (from the c++ level down to the hardward level).

Why isn't it undefined behaviour to destroy an object that was overwritten by placement new?

I'm trying to figure out whether the following is undefined behaviour. I have a feeling it's not UB, but my reading of the standard makes it look like it is UB:
#include <iostream>
struct A {
A() { std::cout << "1"; }
~A() { std::cout << "2"; }
};
int main() {
A a;
new (&a) A;
}
Quoting the C++11 standard:
basic.life¶4 says "A program may end the lifetime of any object by reusing the storage which the object occupies"
So after new (&a) A, the original A object has ended its lifetime.
class.dtor¶11.3 says that "Destructors are invoked implicitly for constructed objects with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl])"
So the destructor for the original A object is invoked implicitly when main exits.
class.dtor¶15 says "the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life])."
So this is undefined behaviour, since the original A no longer exists (even if the new a now exists in the same storage).
The question is whether the destructor for the original A is called, or whether the destructor for the object currently named a is called.
I am aware of basic.life¶7, which says that the name a refers to the new object after the placement new. But class.dtor¶11.3 explicitly says that it's the destructor of the object which exits scope which is called, not the destructor of the object referred to by a name that exits scope.
Am I misreading the standard, or is this actually undefined behaviour?
Edit: Several people have told me not to do this. To clarify, I'm definitely not planning on doing this in production code! This is for a CppQuiz question, which is about corner cases rather than best practices.
You're misreading it.
"Destructors are invoked implicitly for constructed objects" … meaning those that exist and their existence has gone as far as complete construction. Although arguably not entirely spelled out, the original A does not meet this criterion as it is no longer "constructed": it does not exist at all! Only the new/replacement object is automatically destructed, then, at the end of main, as you'd expect.
Otherwise, this form of placement new would be pretty dangerous and of debatable value in the language. However, it's worth pointing out that re-using an actual A in this manner is a bit strange and unusual, if for no other reason than it leads to just this sort of question. Typically you'd placement-new into some bland buffer (like a char[N] or some aligned storage) and then later invoke the destructor yourself too.
Something resembling your example may actually be found at basic.life¶8 — it's UB, but only because someone constructed a T on top of an B; the wording suggests pretty clearly that this is the only problem with the code.
But here's the clincher:
The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..] [basic.life¶3]
Am I misreading the standard, or is this actually undefined behaviour?
None of those. The standard is not unclear but it could be clearer. The intent though is that the new object's destructor is called, as implied in [basic.life]p9.
[class.dtor]p12 isn't very accurate. I asked Core about it and Mike Miller (a very senior member) said:
I wouldn't say that it's a contradiction [[class.dtor]p12 vs [basic.life]p9], but clarification is certainly needed. The destructor description was written slightly naively, without taking into consideration that the original object occupying a bit of automatic storage might have been replaced by a different object occupying that same bit of automatic storage, but the intent was that if a constructor was invoked on that bit of automatic storage to create an object therein - i.e., if control flowed through that declaration - then the destructor will be invoked for the object presumed to occupy that bit of automatic storage when the block is exited - even it it's not the "same" object that was created by the constructor invocation.
I'll update this answer with the CWG issue as soon as it is published. So, your code does not have UB.
Too long for a comment.
Lightness' answer is correct and his link is the proper reference.
But let's examine terminology more precisely. There is
"Storage duration", concerning memory.
"Lifetime", concerning objects.
"Scope", concerning names.
For automatic variables all three coincide, which is why we often do not clearly distinguish: A "variable goes out of scope". That is: The name goes out of scope; if it is an object with automatic storage duration, the destructor is called, ending the lifetime of the named object; and finally the memory is released.
In your example only name scope and storage duration coincide — at any point during its existence the name a refers to valid memory — , while object lifetime is split between two distinct objects at the same memory location and with the same name a.
And no, I think you cannot understand "constructed" in 11.3 as "fully constructed and not destroyed" because the dtor will be called again (wrongly) if the object's lifetime was ended prematurely by a preceding explicit destructor call.
In fact, that's one of the concerns with the concept of memory re-use: If construction of the new object fails with an exception the scope will be left and a destructor call will be attempted on an incomplete object, or on the old object which was deleted already.
I suppose you can imagine the automatically allocated, typed memory marked with a tag "to be destroyed" which is evaluated when the stack is unwound. The C++ runtime does not really track individual objects or their state beyond this simple concept. Since variable names are basically constant addresses it is convenient to think of "the name going out of scope" triggering the destructor call on the named object of the supposed type supposedly present at that location. If one of these suppositions is wrong all bets are off.
Imagine using placement new to create a struct B to the storage where the A a objects lives. In the end of the scope, the destructor of the struct A will be called (because the variable a of type A goes out of scope), even if an object of type B is in reallty living there right now.
As already cited:
"If a program ends the lifetime of an object of type T with static
([basic.stc.static]), thread ([basic.stc.thread]), or automatic
([basic.stc.auto]) storage duration and if T has a non-trivial
destructor,39 the program must ensure that an object of the original
type occupies that same storage location when the implicit destructor
call takes place;"
So after putting B into the a storage, you need to destroy B and put an A there again, to not violate the rule above. This somehow not apply here directly, because you are putting an A to an A, but it shows the behavior. It shows, that this thinking is wrong:
So the destructor for the original A object is invoked implicitly when
main exits.
There is no "original" object any longer. There is just an object currently alive in the storage of a. And thats it. And on the data currently sitting in a, a function is called, namely the destructor of A. Thats what the program compiles to. If it would magically kept track of all "original" objects you would somehow have a dynamic runtime behavior.
Additionally:
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
([expr.delete]) 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.
Since the destructor of A is not trivial and has side effects, (i think) its undefined behavior. For build in types, this does not apply (hence you can use a char buffer as object buffer without reconstructing the chars back into the buffer after using it) since they have a trivial (no-op) destructor.

Do temporary objects have scope?

Names have scope (a compile-time property), while objects have lifetimes (a runtime property). Right?
I often see people talking about temporary objects "going out of scope". But since a temporary object does not have a name, I think it does not make sense to talk about "scope" in this context. The lifetime of a temporary object is very clearly defined and has nothing to do with scope. Would you agree?
Names have scope (a compile-time property),
Yes. I would not call it a property thought. But basically yes.
while objects have lifetimes (a runtime property). Right?
There are three types of variables. Each type has different properties in relation to lifetimes.
Automatic storage duration:
Static storage duration
Dynamic storage duration
Note: automatic storage duration objects have a lifetime that is bound to the scope of the variable.
I often see people talking about temporary objects "going out of scope".
Unless bound to a variable a temporary is destroyed at the end of an expression. If they are bound to a variable (a const reference) then they have the same lifespan as the variable. Sometimes it is just easier to refer to this as the scope, but technically you are correct.
But since a temporary object does not have a name, I think it does not make sense to talk about "scope" in this context.
Technically yes. But I think it just makes talking about it easier. To me (though not technically correct) the scope of a temporary (not bound) is the expression. Its easier to say than the lifespan of the temporary variable.
The lifetime of a temporary object is very clearly defined and has nothing to do with scope. Would you agree?
Yes. But it still feels more natural to talk about scope (even if it is not technically correct). As most people understand what you are trying to imply. But when you get down and talk about the very technical stuff you should use the correct terminology and scope in this context is not correct.
The lifetime of temporaries has very little to do with syntactical blocks, but "scope" — as a word rather than a technical term — can be used in other ways. The important question is whether you are confused when people use "scope" to refer to temporaries. (It doesn't appear that you are, from my POV.)
Since you're talking about using the term to communicate with others, that communication is what's really important. If you were defining terms by writing a standardese document or trying to interpret such a document in the context of defined terms, the situation would be different. Interpreting ISO 14882 will, of course, involve communicating with others, so you would just have to ask for clarification if necessary, in that case.
It's counter-productive to make all non-standardese communication be standardese, and it's often better to use code in either case when high precision is required. The C++ standard extensively uses examples for this reason.
For another example, "calling a constructor" is often used, yet technically you can't call a ctor directly; instead, ctors are part of object initialization. This is why there's an explicit form of new solely to construct an object. (Interestingly, you can call a destructor directly.) However, I would expect that phrase to be understood in most contexts, though I wouldn't advocate using it in standardese contexts.
I've seen people say that "an object went out of scope" when it meant (in your parlance) "the lifetime of the object ended when the object's name went out of scope". If you use that short form, it's natural to say that temporay objects go out of scope, too.
Temporary objects do have names, albeit referable by the compiler only. Otherwise how would the compiler refer to them? Just because you can't refer to a temporary once it's instantiated doesn't mean the compiler can't refer to it.
f(Foo(), Bar());
The compiler has to refer to at least one of the temporaries even though you as a programmer can't refer to either of them. Temporary objects do have a scope.
Binding to a const reference extends the lifetime of a temporary to the lifetime of the reference, so in a sense, it does have something to do with scope in this particular case :
std::string foo();
int main()
{
// Lifetime of the temporary returned by foo is indeed the scope of bar
const std::string &bar = foo();
}
See this article from Herb Sutter :
Normally, a temporary object lasts
only until the end of the full
expression in which it appears.
However, C++ deliberately specifies
that binding a temporary object to a
reference to const on the stack
lengthens the lifetime of the
temporary to the lifetime of the
reference itself, and thus avoids what
would otherwise be a common
dangling-reference error.