46
votes

Given is a class with a static member.

class BaseClass
{
public:
    static std::string bstring;
};

String has obviously to be default-initialized outside of the class.

std::string BaseClass::bstring {"."};

If I include the above line in the header along with the class, I get a symbol multiply defined error. It has to be defined in a separate cpp file, even with include guards or pragma once.

Isn't there a way to define it in the header?

7
Headers are not for initialization. They are for providing interface declarations.Elazar
@Elazar If I have to provide multiple definition files just to initialize single members in multiple classes it's counterproductive, and if I provided a single definition file for multiple headers its counterintuitive. Initializing it in the header would be the most comfortable solution.Appleshell
But that is the way it is.mjs

7 Answers

68
votes

You can't define a static member variable more than once. If you put variable definitions into a header, it is going to be defined in each translation unit where the header is included. Since the include guards are only affecting the compilation of one translation unit, they won't help, either.

However, you can define static member functions! Now, at first sight that may not look as if it could help except, of course, that function can have local static variable and returning a reference to one of these behaves nearly like a static member variable:

static std::string& bstring() { static std::string rc{"."}; return rc; }

The local static variable will be initialized the first time this function is called. That is, the construction is delayed until the function is accessed the first time. Of course, if you use this function to initialize other global objects it may also make sure that the object is constructed in time. If you use multiple threads this may look like a potential data race but it isn't (unless you use C++03): the initialization of the function local static variable is thread-safe.

16
votes

In C++17 you can use inline variables, which you can use even outside classes.

The inline specifier, when used in a decl-specifier-seq of a variable with static storage duration (static class member or namespace-scope variable), declares the variable to be an inline variable.

A static member variable (but not a namespace-scope variable) declared constexpr is implicitly an inline variable.⁽¹⁾

For example:

class Someclass {
public:
    inline static int someVar = 1;
};

Or,

namespace SomeNamespace {
    inline static int someVar = 1;
}

⁽¹⁾ https://en.cppreference.com/w/cpp/language/inline

11
votes

Regarding

Isn't there a way to define [the static data member] in the header?

Yes there is.

template< class Dummy >
struct BaseClass_statics
{
    static std::string bstring;
};

template< class Dummy >
std::string BaseClass_statics<Dummy>::bstring = ".";

class BaseClass
    : public BaseClass_statics<void>
{};

An alternative is to use a function, as Dietmar suggested. Essentially that is a Meyers' singleton (google it).

Edit: Also, since this answer was posted we've got the inline object proposal, which I think is accepted for C++17.

Anyway, think twice about the design here. Globals variables are Evil™. This is essentially a global.

5
votes

To keep the definition of a static value with the declaration in C++11 a nested static structure can be used. In this case the static member is a structure and has to be defined in a .cpp file, but the values are in the header.

class BaseClass
{
public:
  static struct _Static {
     std::string bstring {"."};
  } global;
};

Instead of initializing individual members the whole static structure is initialized:

BaseClass::_Static BaseClass::global;

The values are accessed with

BaseClass::global.bstring;

Note that this solution still suffers from the problem of the order of initialization of the static variables. When a static value is used to initialize another static variable, the first may not be initialized, yet.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

In this case the static variable headers will contain either { "" } or { ".h", ".hpp" }, depending on the order of initialization created by the linker.

3
votes

§3.2.6 and the following paragraphs from the current c++ 17 draft (n4296) define the rules when more than one definition can be present in different translation units:

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then [...]

Obviously definitions of static data members of class type are not considered to appear in multiple translations units. Thus, according to the standard, it is not allowed.

The suggested answers from Cheers and hth. - Alf and Dietmar are more kind of a "hack", exploiting that definitions of

static data member of a class template (14.5.1.3)

and

inline function with external linkage (7.1.2)

are allowed in multiple TU ( FYI: static functions defined inside a class definition have external linkage and are implicitly defined as inline ) .

2
votes

No, it can't be done in a header - at least not if the header is included more than once in your source-files, which appears to be the case, or you wouldn't get an error like that. Just stick it in one of the .cpp files and be done with it.

1
votes

UPDATE: My answer below explains why this cannot be done in the way suggested by the question. There are at least two answers circumventing this; they may or may not solve the problem.


The bstring static member has to be linked to a specific memory address. For this to happen, it has to appear in a single object file, therefore it has to appear in a single cpp file. Unless you're playing with #ifdef's to make sure this happens, what you want cannot be done in the header file, as your header file may be included by more than one cpp files.