How to initialize static members in the header

柔情痞子 提交于 2019-11-29 00:25:04

问题


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?


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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




回答5:


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.




回答6:


§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 ) .




回答7:


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.




回答8:


If the initializer can be expressed as a literal, it is solved in C++11:

inline std::string operator"" _s(const char* p, size_t n) {
    return std::string{p, n};
}

class BaseClass {
    // inline initialization using user-defined literals
    // should allow for multiple definitions
    std::string bstring{"."_s};
};


来源:https://stackoverflow.com/questions/18860895/how-to-initialize-static-members-in-the-header

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!