Date: Wed, 23 Oct 2019 23:04:47 -0700
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.
>
> That's thanks to [basic.start.static]: "Together, zero-initialization
> and constant initialization are called static initialization; all other
> initialization is dynamic initialization. All static initialization
> strongly happens before any dynamic initialization."
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.
> But could the same mutex continue to be used during destruction of
> non-local objects?
So long as it hasn't been destroyed yet. Initialisation/destruction order
rules apply.
> I would have hoped that objects would be destroyed in reverse order, so
> that a mutex would be guaranteed to be destroyed after all objects with
> dynamic initialisation.
>
> But GCC says no:
>
> #include <iostream>
>
> 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'm not sure that the standard rules that out. [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.
> I can see why the latter falls out of an implementation - you just
> register the destructor with `__cxa_atexit` at the point you would have
> called the constructor during your dynamic initialisation code. But is
> it what's intended?
Yes.
> 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.
wrote:
> std::mutex has a constexpr constructor, which guarantees a non-local one
> is usable during construction of non-local objects.
>
> That's thanks to [basic.start.static]: "Together, zero-initialization
> and constant initialization are called static initialization; all other
> initialization is dynamic initialization. All static initialization
> strongly happens before any dynamic initialization."
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.
> But could the same mutex continue to be used during destruction of
> non-local objects?
So long as it hasn't been destroyed yet. Initialisation/destruction order
rules apply.
> I would have hoped that objects would be destroyed in reverse order, so
> that a mutex would be guaranteed to be destroyed after all objects with
> dynamic initialisation.
>
> But GCC says no:
>
> #include <iostream>
>
> 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'm not sure that the standard rules that out. [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.
> I can see why the latter falls out of an implementation - you just
> register the destructor with `__cxa_atexit` at the point you would have
> called the constructor during your dynamic initialisation code. But is
> it what's intended?
Yes.
> 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.
-- Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Software Architect - Intel System Software Products
Received on 2019-10-24 01:07:04