c++staticc++17inline-variable

C++17 inline static member instantiation timing: Lazy instantiation or before main()?


I’m defining an inline static data member in a C++17 class (inline static SharedData gSharedData;).

I have two questions:

1)It seems gSharedData instantiated before main() starts like (non-inline static member) and isnot lazy initialization (first use idiom ). The code below output confirms that. Is this always the case for inline static variables?

2)In a project I am working on (a large codebase which has shared libraries), which is similar to the code below, I'm observing multiple constructor calls from different threads unlike the output of the code below, each printing a different address—suggesting that more than one instance is being created! As far as I know, the instantiation of static variables has been thread-safe since C++11. Any thoughts or ideas why two threads are instantiating same inline static member ?

#include <iostream>
#include <thread>
class SharedData {
public:
    SharedData() {
        std::cout << "Constructor called - Thread ID: " << std::this_thread::get_id()
            << ", Instance Address: " << this << std::endl;
    }
    static void print()
    {
        std::cout << "print() - > Thread ID: " << std::this_thread::get_id() << '\n';
    }


};

class Test
{
public:
    inline static SharedData gSharedData;
};

void f() {

    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    Test::gSharedData.print();
}


int main() {
   
    std::jthread t1(f);
    std::jthread t2(f);
    std::jthread t3(f);
    std::jthread t4(f);
    std::cout << "main()  - > Thread ID: " << std::this_thread::get_id() << '\n';
    return 0;
}

output:

Constructor called - Thread ID: 38168, Instance Address: 007C03D0
main()  - > Thread ID: 38168
print() - > Thread ID: 37504
print() - > Thread ID: 32244
print() - > Thread ID: 39416
print() - > Thread ID: 31356

Solution

  • 1)It seems to be gSharedData instantiated before main() starts like (non-inline static member) and isnot lazy initialization (first use idiom ). Is this always the case for inline static variables?

    inline static member initialization has the same initialization as a non-inline one, it should complete before you use it in main, see When are static C++ class members initialized? . Specifically if the system lazy-loads the library then it may not be initialized before main starts, but it needs to be completely initialized when you use that variable inside main . Using the variable doesn't "cause the initialization", it is just that it "will have happened" by the time you use it.

    2)In a project I am working on (a large codebase), which is similar to the code below, I'm observing multiple constructor calls from different threads, each printing a different address—suggesting that more than one instance is being created

    Each translation unit that includes this header creates its own copy of the object, then the linker removes duplicates when it is creating each shared library, the linker works on each shared library in isolation, and each shared library that included this header will have its own copy of it.

    there are platform-dependent ways to make only 1 copy "visible" like windows __declspec(dllimport) or linux's symbol table, but linux symbol tables are not perfect and some implementations caused multiple constructors/destructors to be invoked in different shared libraries, even though at runtime all pointers refer to only a single copy and only 1 copy is "visible".

    The most portable way to guarantee only 1 copy of a global exist is to put it in a single translation unit that is compiled into only a single shared library, mostly implying non-inline, but even non-inline ones can run into issues when used in static libraries that are included into multiple shared libraries, so just put them in a .cpp file that will be compiled to a single dll/so or use meyer's singleton on a function that is exported from a single dll/so