How to have static data members in a header-only library? [duplicate]
Asked Answered
C

3

65

What is the best way to have a static member in a non-templated library class, without placing the burden of defining the member on the class user?

Say I want to provide this class:

class i_want_a_static_member
{
    static expensive_resource static_resource_;

public:
    void foo()
    {
        static_resource_.bar();
    }
};

Then the user of the class must not forget to define the static member somewhere (as already answered many times):

// this must be done somewhere in a translation unit
expensive_resource i_want_a_static_member::static_resource_;

I do have an answer below, but it has some disadvantages. Are there better and/or more elegant solutions?

Cordovan answered 29/7, 2012 at 14:6 Comment(8)
When you say "non-templated", do you mean you are forced not to use any templates, or just that the main classes don't happen to be templated?Censorious
@VaughnCato I just don't want the class user have to deal with a templated class. Maybe it just makes no sense to introduce a template parameter for the class i_want_a_static_member.Cordovan
Ok, but if it is a helper class that the user doesn't have to deal with, then it is ok for it to be templated?Censorious
@VaughnCato Yes, that's okay. You can see in my own answer that I'm using a templated helper class, too. But I want to provide a class that the user can deal with (that's the reason for providing it).Cordovan
Yes, I see. Another thing you can do is use an inline member function with a static local variable and the member function just returns a reference to it.Censorious
The real question is: why header-only? That's just a source of problems for you (since you're going to get bug reports for compilers you've never heard of) and the client (since he's going to see his compile times go up seriously).Cuman
@JamesKanze Header-only or not is a difficult point to discuss. In my case the library is used only by a dozen in-house programmers with three compilers, all of which I have access to. As for header-only: for one of our compilers there is no official Boost port yet. While the header-only parts of Boost usually work out-of-the-box, using the rest of Boost would take a considerable effort, so maybe you can see why I like header-only libraries despite their disadvantages. Though for the above example it's perhaps just laziness to stay header-only.Cordovan
@Cordovan Getting all of Boost to work is a problem, if no one else has done it for you. But you don't have to be as complicated as Boost. If you only have a few targeted systems, to which you have access to the compilers, it shouldn't be too hard for you to deliver compiled libraries. After all, you're building the libraries anyway in order to test them.Cuman
F
82

C++17 and above

Use inline static variables for non-dynamic initialization:

struct Foo
{
    inline static int I = 0;
};

And use function local static variables otherwise:

struct Foo
{
    static std::string& Bar()
    {
        static std::string S = compute();
        return S;
    }
};

C++14 and below

Use function local statics, as they are plain easier to use.

If for some reason you really wish for a static data member, then you can use the template trick:

template <typename T = void>
struct Foo
{
     static int I = 0; // inline initialization only for simple types.
};

template <typename T>
int Foo<T>::I;

On local statics

For resources which require dynamic initialization, it is best to use a local static.

The order in which file-scope or class-scope statics are dynamically initialized is undefined, in general, leading to the Static Initialization Order Fiasco when you try to read a uninitialized static as part of the initialization of another. Local static solve the issue by being initialized lazily, on first use.

There is some slight overhead to using local statics, however. From C++11 onwards, the initialization is required to be thread-safe, which typically means that any access is gated by an atomic read and well-predicted branch.

Filar answered 29/7, 2012 at 16:53 Comment(4)
@SrinathSridhar: Not much to explain, this is just a feature of C++. When a variable at function scope is declared with the static storage qualifier then the language that one and only one instance is created. This instance is lazy-initialized the first time that flow-control pass through its declaration, deterministically.Filar
This is not thread safe until C++11 spec. Even now that the spec is out, not all compilers support thread safe static initialization yet. For example, neither MS Visual Studio 2012 or 2013 support what they call "magic statics".Sculley
@MicahCaldwell: Thanks for the remark, I have not been using Visual Studio for ages. It's quite unfortunate since it's been thread-safe in gcc for a long time (even before C++11).Filar
@prehistoricpenguin: If the initialization is dynamic (not constexpr), then it occurs on the first use of the variable. This is great for faster start-up of binaries, but may cause a latency bump on the first call and delay error detection.Filar
C
18

My own solution is to use a templated holder class, as static members work fine in templates, and use this holder as a base class.

template <typename T>
struct static_holder
{
    static T static_resource_;
};

template <typename T>
T static_holder<T>::static_resource_;

Now use the holder class:

class expensive_resource { /*...*/ };

class i_want_a_static_member : private static_holder<expensive_resource>
{
public:
    void foo()
    {
        static_resource_.bar();
    }
};

But as the name of the member is specified in the holder class, you can't use the same holder for more than one static member.

Cordovan answered 29/7, 2012 at 14:6 Comment(2)
Note: actually, you could use composition instead of inheritance. struct A { static_holder<B> x; static_holder<B> y; }; would work, although it would not take advantage of Empty Base Optimization.Filar
This doesn't run into the same thread unsafe problems as the function local static ("magic statics") solution above right?Eyehole
E
7

As of C++ 17. You can now use inline variables to do this:

static const inline float foo = 1.25f;
Encephalogram answered 13/11, 2018 at 22:22 Comment(5)
On behalf of header purists everywhere, THANK YOU!Ician
Note "static inline" creates different variables in different unit translations. "inline" (without static) creates a "true" global variable. See en.cppreference.com/w/cpp/language/inline. Also I would drop "const" from this answer as it's a special case.Diplomatics
Can you elaborate on where you get "Note "static inline" creates different variables in different unit translations. "inline" (without static) creates a "true" global variable." from your cppreference link? I skimmed over it and didn't see where that came from.Encephalogram
sorry, but I think that "upgrade to C++17" is not a viable way 99% of the time. Good point, though.Requirement
@redfeatherplusplus: ARA wasn't wrong, but wasn't talking about members as the question clearly concerns. ARA's statement is true of variables which a header file defines in namespace scope.Henig

© 2022 - 2024 — McMap. All rights reserved.