64

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?

Community
  • 1
  • 1
pesche
  • 3,054
  • 4
  • 34
  • 35
  • 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? – Vaughn Cato Jul 29 '12 at 14:12
  • @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*. – pesche Jul 29 '12 at 14:15
  • 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? – Vaughn Cato Jul 29 '12 at 14:16
  • @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). – pesche Jul 29 '12 at 14:20
  • 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. – Vaughn Cato Jul 29 '12 at 14:49
  • 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). – James Kanze Jul 29 '12 at 15:36
  • @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. – pesche Jul 30 '12 at 19:34
  • @pesche 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. – James Kanze Jul 31 '12 at 07:20

3 Answers3

80

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 5
    @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. – Matthieu M. Aug 12 '13 at 18:38
  • 3
    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". – Micah Zoltu Aug 19 '13 at 00:21
  • 4
    @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). – Matthieu M. Aug 19 '13 at 06:28
  • 1
    @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. – Matthieu M. May 21 '19 at 06:38
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.

pesche
  • 3,054
  • 4
  • 34
  • 35
  • 7
    Note: actually, you could use composition instead of inheritance. `struct A { static_holder x; static_holder y; };` would work, although it would not take advantage of Empty Base Optimization. – Matthieu M. Aug 13 '13 at 06:12
  • This doesn't run into the same thread unsafe problems as the function local static ("magic statics") solution above right? – solstice333 Jun 11 '19 at 23:02
7

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

static const inline float foo = 1.25f;
  • 1
    On behalf of header purists everywhere, THANK YOU! – Syndog Dec 21 '18 at 19:23
  • 1
    Note "static inline" creates different variables in different unit translations. "inline" (without static) creates a "true" global variable. See https://en.cppreference.com/w/cpp/language/inline. Also I would drop "const" from this answer as it's a special case. – ARA1307 Dec 25 '18 at 02:58
  • 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. – redfeatherplusplus Jan 04 '19 at 17:30
  • sorry, but I think that "upgrade to C++17" is not a viable way 99% of the time. Good point, though. – fiorentinoing Feb 08 '19 at 12:55
  • @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. – Ben Voigt Mar 07 '23 at 22:56