如何初始化标头中的静态成员

问题描述 投票:33回答:8

给定是一个具有静态成员的类。

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

字符串显然是在类之外进行默认初始化。

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

如果我在标题中包含上面的行和类,我得到一个symbol multiply defined错误。它必须在单独的cpp文件中定义,即使使用include guardspragma once

有没有办法在标题中定义它?

c++ class static-members
8个回答
55
投票

您不能多次定义static成员变量。如果将变量定义放入标题中,则将在包含标题的每个转换单元中定义它。由于包含警卫只影响一个翻译单元的编译,他们也无济于事。

但是,您可以定义static成员函数!现在,初看起来可能看起来不像它可能会有所帮助,当然,除了该函数可以有本地static变量并且返回对这些行为之一的引用几乎就像一个static成员变量:

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

第一次调用此函数时,将初始化本地static变量。也就是说,构造被延迟直到第一次访问该功能。当然,如果使用此函数初始化其他全局对象,它还可以确保对象是及时构造的。如果你使用多个线程,这可能看起来像一个潜在的数据竞争,但它不是(除非你使用C ++ 03):函数本地static变量的初始化是线程安全的。


10
投票

关于

“有没有办法在标题中定义[静态数据成员]?

就在这里。

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>
{};

另一种方法是使用一种功能,正如Dietmar建议的那样。基本上这是迈耶斯的单身人士(google it)。

编辑:此外,由于这个答案已经发布,我们已经有了内联对象提案,我认为这个提议已被C ++ 17接受。

无论如何,在这里三思而后行。 Globals变量是Evil™。这基本上是一个全球性的。


5
投票

在C ++ 17中,您可以使用内联变量,甚至可以在类外使用它们。

内联说明符在具有静态存储持续时间(静态类成员或命名空间范围变量)的变量的decl-specifier-seq中使用时,将变量声明为内联变量。

声明constexpr的静态成员变量(但不是命名空间范围变量)隐式地是内联变量.⁽¹⁾

例如:

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

要么,

namespace SomeNamespace {
    inline static int someVar = 1;
}

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


4
投票

为了使用C ++ 11中的声明来保持静态值的定义,可以使用嵌套的静态结构。在这种情况下,静态成员是一个结构,必须在.cpp文件中定义,但值在标题中。

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

不是初始化单个成员,而是初始化整个静态结构:

BaseClass::_Static BaseClass::global;

可以使用访问这些值

BaseClass::global.bstring;

请注意,此解决方案仍然存在静态变量初始化顺序的问题。当静态值用于初始化另一个静态变量时,第一个静态变量可能尚未初始化。

// 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 };

在这种情况下,静态变量头将包含{“”}或{“。h”,“。hpp”},具体取决于链接器创建的初始化顺序。


3
投票

不,它不能在标题中完成 - 至少如果标题在源文件中包含多次,这似乎是这种情况,或者你不会得到这样的错误。只需将其粘贴在其中一个.cpp文件中即可完成。


3
投票

§3.2.6和当前c ++ 17草案(n4296)中的以下段落定义了在不同翻译单元中可以存在多个定义时的规则:

类类型(第9条),枚举类型(7.2),带内部链接的内联函数(7.1.2),类模板(第14条),非静态函数模板(14.5.6)可以有多个定义,类模板的静态数据成员(14.5.1.3),类模板的成员函数(14.5.1.1),或者在程序中未指定某些模板参数(14.7,14.5.5)的模板特化,前提是每个模板定义出现在不同的翻译单元中,并且定义满足以下要求。鉴于这样一个名为D的实体在多个翻译单元中定义,那么[...]

显然,类型的静态数据成员的定义不被视为出现在多个翻译单元中。因此,根据标准,不允许这样做。

干杯和赫斯的建议答案。 - Alf和Dietmar更像是一种“黑客”,利用了这种定义

类模板的静态数据成员(14.5.1.3)

具有外部链接的内联函数(7.1.2)

允许多个TU(FYI:在类定义中定义的静态函数具有外部链接,并隐式定义为内联)。


1
投票

更新:我在下面的回答解释了为什么不能以问题建议的方式做到这一点。绕过这个至少有两个答案;他们可能会也可能不会解决问题。


bstring静态成员必须链接到特定的内存地址。为此,它必须出现在单个目标文件中,因此它必须出现在单个cpp文件中。除非你正在使用#ifdef来确保发生这种情况,否则你想要的不能在头文件中完成,因为你的头文件可能包含在多个cpp文件中。


-1
投票

如果初始化器可以表示为文字,则可以在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};
};
© www.soinside.com 2019 - 2024. All rights reserved.