给定是一个具有静态成员的类。
class BaseClass
{
public:
static std::string bstring;
};
字符串显然是在类之外进行默认初始化。
std::string BaseClass::bstring {"."};
如果我在标题中包含上面的行和类,我得到一个symbol multiply defined
错误。它必须在单独的cpp
文件中定义,即使使用include guards
或pragma once
。
有没有办法在标题中定义它?
您不能多次定义static
成员变量。如果将变量定义放入标题中,则将在包含标题的每个转换单元中定义它。由于包含警卫只影响一个翻译单元的编译,他们也无济于事。
但是,您可以定义static
成员函数!现在,初看起来可能看起来不像它可能会有所帮助,当然,除了该函数可以有本地static
变量并且返回对这些行为之一的引用几乎就像一个static
成员变量:
static std::string& bstring() { static std::string rc{"."}; return rc; }
第一次调用此函数时,将初始化本地static
变量。也就是说,构造被延迟直到第一次访问该功能。当然,如果使用此函数初始化其他全局对象,它还可以确保对象是及时构造的。如果你使用多个线程,这可能看起来像一个潜在的数据竞争,但它不是(除非你使用C ++ 03):函数本地static
变量的初始化是线程安全的。
关于
“有没有办法在标题中定义[静态数据成员]?
就在这里。
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™。这基本上是一个全球性的。
在C ++ 17中,您可以使用内联变量,甚至可以在类外使用它们。
内联说明符在具有静态存储持续时间(静态类成员或命名空间范围变量)的变量的decl-specifier-seq中使用时,将变量声明为内联变量。
声明constexpr的静态成员变量(但不是命名空间范围变量)隐式地是内联变量.⁽¹⁾
例如:
class Someclass {
public:
inline static int someVar = 1;
};
要么,
namespace SomeNamespace {
inline static int someVar = 1;
}
为了使用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”},具体取决于链接器创建的初始化顺序。
不,它不能在标题中完成 - 至少如果标题在源文件中包含多次,这似乎是这种情况,或者你不会得到这样的错误。只需将其粘贴在其中一个.cpp文件中即可完成。
§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:在类定义中定义的静态函数具有外部链接,并隐式定义为内联)。
更新:我在下面的回答解释了为什么不能以问题建议的方式做到这一点。绕过这个至少有两个答案;他们可能会也可能不会解决问题。
bstring
静态成员必须链接到特定的内存地址。为此,它必须出现在单个目标文件中,因此它必须出现在单个cpp
文件中。除非你正在使用#ifdef
来确保发生这种情况,否则你想要的不能在头文件中完成,因为你的头文件可能包含在多个cpp
文件中。
如果初始化器可以表示为文字,则可以在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};
};