C++ Local Variable Destruction Order

C++ local variable destruction order

Within each category of storage classes (except dynamically allocated objects), objects are destructed in the reverse order of construction.

Ensure the construction and destruction order of static variables in c++

In your case you are using construct on first use, and none of the constructors of your classes dependent on the other. Thus the order of initialization is guaranteed A then B.

The destruction order in this case it is guaranteed to be as such B->A, as long as you have simple destructors. Here is a more elaborate answer.

Why is the order of destruction of these function-local static objects NOT the inverse of their order of initialization?

The actual Standard text in C++14 [basic.start.term] is:

If the completion of the constructor or dynamic initialization of an object with static storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first. [Note: This definition permits concurrent destruction. —end note ]

In your code, two is constructed during the constructor of one. Therefore the completion of constructor of two is sequenced-before the completion of constructor of one.

So the completion of the destructor of one is sequenced-before the completion of the destructor of two, which explains what you are seeing.

Why is the order of destruction same as order of construction, with static object (C++)?

Static local variables will be destroyed at program exit.

The destructor for a block-scope static variable is called at program exit, but only if the initialization took place successfully.

So b will be destoryed firstly at the end of main(), a will be destroyed after that.

For the initialization,

are initialized the first time control passes through their declaration

So b will be initialized firstly in main(), then a gets initialized.

Destruction order of static objects in C++

The static objects are destructed in the reverse order of construction. And the order of construction is very hard to control. The only thing you can be sure of is that two objects defined in the same compilation unit will be constructed in the order of definition. Anything else is more or less random.

How is destroying local variables when a block is exited normally called in C++?

An object is automatically destructed when it "goes out of scope". This could be referred to as "automatic storage reclamation", but that actually refers to garbage collection (there are several papers with that phrase in their name that use the term to mean garbage collection). When it is used to ensure proper pairing of open/close, lock/unlock, or other forms of resource acquisition with their appropriate release, then it is known as the design pattern of Resource Acquisition is Initialization (RAII), which is somewhat ironic given that the main aspect of RAII is not the resource initialization or acquisition, but rather its destruction.

Local variables construction and destruction with optimizer involved

The only guarantees are that any observable side effects (that is, reads and writes to volatile objects and calls to I/O functions) of the construction of a will happen before any observable side effects of the construction of b, and any side effects of a required by b will happen before they are needed.

It's hard to imagine why you would need a stricter ordering than that, but making the objects volatile will ensure that a is completely initialised before initialising any part of b, although some code from the constructor could still happen before a is complete.

Does returning a local variable return a copy and destroy the original(nrvo)?

Does returning a local variable return a copy and destroy the original?

The final answer to your question is that it depends on whether or not optimization is enabled. So lets discuss each case separately. Note also that since the given output in the original question is for C++17, the below discussion is also for the same(C++17 & onwards).

With Optimization

Here we will see what happens when optimization(NRVO) is enabled.

class test {
public:
test(int p) {
cout << "The constructor ( test(int p) ) was called: "<<this<<endl;
}
test(test&&c)noexcept {
cout << "The constructor ( test(test && c) ) was called: "<<this << endl;
}
~test() {
cout << "The distructor was called: "<<this << endl;
}
};
test function() {
test i(8);
return i;
}
int main()
{
test o=function();
return 0;
}

The output of the program is(with NRVO enabled):

The constructor ( test(int p) ) was called: 0x7fff78e42887   <-----object o construction
The distructor was called: 0x7fff78e42887 <-----object o destruction

The above output can be understood using the optimization called named return value optimization(aka NRVO) as described in copy elison which states:

Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

  • In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".

(emphasis mine)

Lets apply this to our example given above and try to understand the output. The variable named i is a local variable meaning it has automatic storage duration and thus according to the above quoted statement, the compilers are allowed(but not required!) to directly construct the object into the storage for variable named o. That is, it is as if you wrote:

test o(5); //equivalent to this due to NRVO

Thus here we first see the call to the converting constructor test::test(int) for object o and then the destructor call for that object o.

Without Optimization

You have the option to disable this optimization by using the -fno-elide-constructors flag. And when executing the same program with this flag, the output of the program will become:

The constructor ( test(int p) ) was called: 0x7ffda9d94fe7        <-----object i construction
The constructor ( test(test && c) ) was called: 0x7ffda9d95007 <-----object o construction
The distructor was called: 0x7ffda9d94fe7 <-----object i destruction
The distructor was called: 0x7ffda9d95007 <-----object o destruction

This time since we have supplied the -fno-elide-constructors flag to the compiler, NRVO is disabled. This means that now the compiler cannot omit the copy/move construction corresponding to the return statement return i;. This in turn means that first the object i will be constructed using the converting constructor test::test(int) and thus we see the very first line in the output.

Next, this local variable named i will be moved using the move constructor test::test(test&&) and hence we see the second line of the output. Note that the object o will be constructed directly from this moved prvalue directly due to mandatory copy elison since you're using C++17.

Next, the local variable i will be destructed using the destructor test::~test() and we see the third line in the output.

Finally, the object o will get destroyed and we see the fourth line of the output.

In this case, it is as-if you wrote:

test o = std::move(test(5)); //equivalent to this

Destruction Order of Meyers Singletons

The point of this construct is to impose a construction order (and thus a destruction order).

Construction

Since these are local static variables, the order of construction is determined by the order in which their respective Instance functions are called for the first time.

Since that is done in main, the construction order is fully specified.

The only way to make the order unspecified is if you use them in static initialisation in different translation units, for instance if one has

C& the_c = C::Instance();

and the other has

D& the_d = D::Instance();

Destruction

The destruction of objects with static storage is the reverse of the order of construction.

3.6.3, Termination, paragraph 1:

If the completion of the constructor or dynamic initialization of an
object with static storage duration is sequenced before that of
another, the completion of the destructor of the second is sequenced
before the initiation of the destructor of the first.

So the destruction order is fully specified by the construction order.

Note that this singleton construct is well specified even if one of them depends on the other, regardless of translation unit.

That is, this is perfectly safe, and it doesn't matter where it's defined:

class C {
public:
static C& Instance() {
static C c(D::Instance());
return c;
}

~C(){ m_d.doSomething(); } // Yes, this is safe.
private:
C(D& d) : m_d(d) { m_d.doSomething(); } // Yes, this is safe.
D& m_d;
};


Related Topics



Leave a reply



Submit