51

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

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

String 显然必须在类之外进行默认初始化。

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

如果我将上述行与类一起包含在标题中,则会出现symbol multiply defined错误。它必须在单独的cpp文件中定义,即使使用include guardsor pragma once

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

4

7 回答 7

72

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

但是,您可以定义static成员函数!现在,乍一看,它可能没有帮助,当然,该函数可以具有局部static变量并返回对其中一个变量的引用几乎就像一个static成员变量:

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

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

于 2013-09-17T22:38:51.350 回答
25

在 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

于 2019-03-05T01:16:38.800 回答
11

关于

没有办法在header中定义【静态数据成员】吗?

就在这里。

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 建议的那样,另一种方法是使用函数。本质上这是一个迈耶斯的单身(谷歌它)。

编辑:另外,由于发布了这个答案,我们得到了内联对象提案,我认为它被 C++17 接受。

无论如何,请三思这里的设计。全局变量是 Evil™。这本质上是一个全球性的。

于 2014-11-21T21:00:39.900 回答
5

为了在 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" },具体取决于链接器创建的初始化顺序。

于 2017-02-23T18:38:03.967 回答
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 的实体在多个翻译单元中定义,那么 [...]

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

来自 Cheers and hth 的建议答案。- Alf 和 Dietmar 更像是一种“黑客”,利用了

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

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

允许在多个 TU 中使用(仅供参考:在类定义中定义的静态函数具有外部链接并隐式定义为 inline )。

于 2017-02-06T14:04:49.837 回答
2

不,它不能在标头中完成 - 至少如果标头在您的源文件中包含不止一次,情况似乎就是这样,否则您不会收到这样的错误。只需将其粘贴在其中一个 .cpp 文件中即可。

于 2013-09-17T22:30:59.247 回答
1

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


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

于 2013-09-17T22:31:41.933 回答