C++ Logo


Advanced search

Re: Destruction order of statically-initialized objects (like std::mutex)

From: Kevin Bracey <kevin_at_[hidden]>
Date: Thu, 24 Oct 2019 11:02:34 +0300
On 24/10/2019 09:04, Thiago Macieira via Std-Discussion wrote:
> On Wednesday, 23 October 2019 09:58:50 PDT Kevin Bracey via Std-Discussion
> wrote:
>> std::mutex has a constexpr constructor, which guarantees a non-local one
>> is usable during construction of non-local objects.
> Unfortunately, it may have a non-constexpr destructor, meaning it is not a
> literal type. That means that you still get dynamic initialisation of its
> atexit destruction and the regular initialisation order problems apply.

Citation here? As far as I can see there's no "regular initialisation
order problem" - having the constexpr constructor means it must be
initialised first. I think we only have destruction order problems. I
don't see any "literal type" constraints.

"Dynamic initialisation of atexit destruction" isn't standardese - I can
see what the practical problem is, but I'm trying to pin down the
specific wording in terms of destruction execution order.

There are potential other odd results - if MyDynamic's constructor can
use std::mutex, it could also choose to exit after doing so, and it
would be very strange if that exit didn't run std::mutex's destructor
because it hasn't been registered yet - can you really skip the
destruction of an object you've already legally used?

>> struct MyMutex
>> {
>> constexpr MyMutex() = default;
>> ~MyMutex() { std::cout << "Destroy MyMutex\n"; }
>> };
>> struct MyDynamic
>> {
>> MyDynamic() { std::cout << "Construct MyDynamic\n"; }
>> ~MyDynamic() { std::cout << "Destroy MyDynamic\n"; }
>> };
>> MyDynamic my_dynamic;
>> MyMutex my_mutex;
>> void main() {}
>> // Output:
>> // Construct MyDynamic
>> // Destroy MyMutex
>> // Destroy MyDynamic
> Seems right to me. MyDynamic is constructed first, then MyMutex; destruction
> is reverse, so MyMutex first then MyDynamic.

I believe MyMutex is constructed first, because it's
constant-initialized, unless there's an exception to
[basic.start.static] I've missed; so destruction is not reverse.

>> [basic.start.term] says
>> "If the completion of the constructor or dynamic initialization of an
>> object with static storage duration strongly happens before that of
>> another, the completion of the destructor of the second is sequenced
>> before the initiation of the destructor of the first. [...] If an object
>> is initialized statically, the object is destroyed in the same order as
>> if the object was dynamically initialized."
>> What does that "as if" mean? "Obey the ordering rules for
>> dynamically-initialised objects, bearing in mind that static objects are
>> initialised first" or "Schedule destruction based on the order this
>> would have been dynamically initialised"?
> The latter, which is the behaviour your code shows.

If you're believing MyMutex is constructed second, as you said above,
then my example code and its output doesn't relate to the question -
obviously Dynamic, Mutex, ~Mutex, Dynamic would be correct - no-one is
ever going to object to reverse-order destruction. I'm asking if the
non-reverse-order Mutex, Dynamic, ~Mutex, ~Dynamic is correct.

MyMutex absolutely must be constructed first, or std::mutex's design
spent effort in vain. Cppreference believes it works: "Because the
default constructor is constexpr, static mutexes are initialized as part
of static non-local initialization, before any dynamic non-local
initialization begins. This makes it safe to lock a mutex in a
constructor of any static object. "

Cppreference doesn't seem to say anything about destructors for
statically-initialized objects, except this interpretation, which I was
considering the "as if" clause might be intending: "if the compiler
opted to lift dynamic initialization of an object to the static
initialization phase of non-local initialization, the sequencing of
destruction honors its would-be dynamic initialization. "

But that's a different case to an object where early static
initialization is required.

>> Shouldn't all statically-initialised objects get first dibs on atexit
>> registration? Otherwise std::mutex's constexpr constructor only does
>> half the ordering job.
> No, because it's impossible to do that. You'd still interleave the
> registration of the destructors after the constructors of another TU.
It's possible if you have a priority-based init arrays, which exist in
some systems. Each TU can put an entry into "_init_array.0" which
registers any destructors for statically-initialized objects, and/or an
entry into the normal "_init_array" which runs the constructors and
registers destructors for dynamically-initialized objects. The linker
can sort those across TUs into a single array.

GCC already exposes manual priority-based init via
__attribute__(constructor(priority)), so the linker/system support
probably exists quite generally.


Received on 2019-10-24 03:04:54