Search code examples
c++static-membersmultiple-definition-error

Why can't I initialize a static class member in a common header file?


Suppose I have a header only library. I have simplified it to something like this.

Header only library Foo.hpp

#ifndef FOO_HPP
#define FOO_HPP

struct Foo{
    static const int A;
};

const int Foo::A = 100;

void SomeMethod(){
   // do some stuff
}
#endif

Then, I have parent class (Parent.hpp and Parent.cpp):

#ifndef PARENT_HPP
#define PARENT_HPP

#include "Foo.hpp"

struct Parent {
        virtual void my_method();
};
#endif

Child class (Child.hpp and Child.cpp)

#ifndef CHILD_HPP
#define CHILD_HPP

#include "Parent.hpp"
#include "Foo.hpp"

struct Child : Parent{
        void my_method();
};
#endif  

In my_method(), I just print the Foo::A variable.

In the real code base, I have template in the header only library.

When I compile this, I got a "multiple definition" error. How to fix this?


Solution

  • You have chosen the "non-inline" mode of initializing your static member. That is perfectly legitimate, but - in this case, you need to have exactly one translation unit (e.g. compiled .cpp file) defining your member. Otherwise, the C++ linker sees one definition of Foo::A in parent.o and a second definition in child.o, and those conflict.

    Thus,

    Solution 1: Move the definition to another file

    1. Create a Foo.cpp, which includes Foo.hpp and defines Foo::A.
    2. Delete the definition from the header, so it isn't repeated in multiple locations

    Solution 2: Make the definition conditional

    This is not really recommended, but it does work.

    1. Surround the definition like so:

      #ifdef FOO_A_DEFINITION
      const int Foo::A = 100;
      #endif
      
    2. Create a Foo.cpp, which defines #FOO_A_DEFINITION and then includes Foo.hpp

    This has the detriment of using macros, but the benefit of human users seeing the definition from the header.

    Solution 3: Initialize in class definition

    So, you are asking yourself "Why should they conflict? I want the compiler to know that they're the same variable!"

    One way to do this to initialize the static member within the class body. Since this is a simple type, that's possible even with older versions of the language standard. This is also @idmean's suggested solution:

    class Foo {
        public:
            static const int A = 100;
    };
    

    There's a caveat here, which is that you're "not really" defining the static member this way. You can use it, but not in every way you would use a variable defined with solutions (1.) or (2.). See discussion here:

    See a discussion of this point here:

    Defining static const integer members in class definition

    Solution 4: Inline your static member

    This solution is another way to tell the compiler "it's just one definition for everybody", which only works in C++17 or later:

    #include <iostream>
    class Foo {
        public:
            inline static const int A = 100;
    };
    

    This looks very similar to Solution 3, but actually does something very different!

    With this solution, you're actually defining the static member multiple times - in each translation unit - but are telling the compiler to only keep a single definition when linking and encountering many of them.

    Solutions (1.) and (2.) behave differently from this solution (4.) when you use dynamically-linked libraries. See:

    Where to initialize static const member in c++17 or newer?

    for an explanation.