Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static inline members initialization order

A well known problem in C++ is the static initialization order fiasco. Is it still considered a problem when one use C++17 static inline members?

Here an example where a static inline member is used in two different translation units (a.cpp and b.cpp) as an initializer for two non-inline static members:

counter.hh

#pragma once

#include <vector>
#include <fstream>

class Counter
{
    public:
        Counter()  { std::ofstream os("o.txt", std::ofstream::app); os << "Counter created" << std::endl; }
        ~Counter() { std::ofstream os("o.txt", std::ofstream::app); os << "Counter destroyed" << std::endl; }
        void add_instance()    
        { 
            ++m_instances; 
            std::ofstream os("o.txt", std::ofstream::app); os << "Counter increased: " << m_instances << std::endl; 
        }
        void remove_instance() 
        { 
            --m_instances; 
            std::ofstream os("o.txt", std::ofstream::app); os << "Counter decreased: " << m_instances << std::endl; 
        }

    private:
        int m_instances = 0;
};

class Object
{
    public:
        Object(Counter & counter) : m_counter(counter) 
        {
            m_counter.add_instance(); 
            std::ofstream os("o.txt", std::ofstream::app); os << "Object created" << std::endl; 
        }
        ~Object() 
        { 
            m_counter.remove_instance(); 
            std::ofstream os("o.txt", std::ofstream::app); os << "Object destroyed" << std::endl; 
        }

    private:
        Counter & m_counter;
};

struct C
{
    static inline Counter static_counter{};
};

a.hh

#pragma once

#include "counter.hh"

struct A
{
    static Object static_a; //not inline
};

a.cpp

#include "a.hh"

Object A::static_a{C::static_counter};

b.hh

#pragma once

#include "counter.hh"

struct B
{
    static Object static_b; //not inline
};

b.cpp

#include "b.hh"

Object B::static_b{C::static_counter};

main.cpp

#include "a.hh"
#include "b.hh"

int main() { }

output (with MSVC 16.1.2)

Counter created
Counter increased: 1
Object created
Counter increased: 2
Object created
Counter decreased: 1
Object destroyed
Counter decreased: 0
Object destroyed
Counter destroyed

I think that, with regard to the initialization, this practice is safe because the C++17 standard ensures that static inline members are: (1) always initialized before any use and (2) initialized only once across multiple translation units.

But I'd like to know if there are any hidden downsides in this pattern, for example related to the order of destruction of each variable across different TUs. Is it well-defined that both static_a and static_b are always destroyed before static_counter?

like image 901
Tarquiscani Avatar asked Jun 09 '19 14:06

Tarquiscani


People also ask

What are static member variables and their initialization in C++?

C++ static member variables and their initialization. Static C++ member variables are defined using the static keyword. The static member variables in a class are shared by all the class objects as there is only one copy of them in the memory, regardless of the number of objects of the class. The static class member variables are initialized ...

What is the static initialization order fiasco?

The static initialization order fiasco refers to the ambiguity in the order that objects with static storage duration in different translation units are initialized in.

How many static member variables are shared by a class object?

The static member variables in a class are shared by all the class objects as there is only one copy of them in the memory, regardless of the number of objects of the class.

What happens if you initialize objects in the wrong order?

If an object in one translation unit relies on an object in another translation unit already being initialized, a crash can occur if the compiler decides to initialize them in the wrong order. For example, the order in which .cpp files are specified on the command line may alter this order.


1 Answers

Yes, this is fine, since in every translation unit static_counter is defined before static_a/static_b. Destruction order is not guaranteed to be the reverse (given threads, this is meaningless anyway), but the reverse of each guarantee holds, so that works too.

like image 62
Davis Herring Avatar answered Sep 21 '22 20:09

Davis Herring