初始化私有静态成员

在C ++中初始化私有静态数据成员的最佳方法是什么? 我试过这个,但它给了我怪异的链接器错误:

class foo { private: static int i; }; int foo::i = 0; 

我猜这是因为我不能从课堂外初始化私人成员。 那么最好的办法是什么?

类声明应该在头文件中(或者如果不共享,则在源文件中)。
文件:foo.h

 class foo { private: static int i; }; 

但是初始化应该在源文件中。
文件:foo.cpp

 int foo::i = 0; 

如果初始化位于头文件中,则包含头文件的每个文件都将具有静态成员的定义。 因此,在链接阶段,你将得到链接器错误,因为初始化variables的代码将在多个源文件中定义。

注意: Matt Curtis指出,如果静态成员variables是const inttypes(例如intboolchar ),那么C ++允许简化上述内容。 然后,您可以直接在头文件的类声明中声明和初始化成员variables:

 class foo { private: static int const i = 42; }; 

对于一个variables

foo.h中:

 class foo { private: static int i; }; 

Foo.cpp中:

 int foo::i = 0; 

这是因为程序中只能有一个foo::i实例。 这是一个头文件和int i在源文件中的extern int i的等价物。

对于一个常数,你可以把这个值直接放在类声明中:

 class foo { private: static int i; const static int a = 42; }; 

对于这个问题的未来观众,我想指出,你应该避免monkey0506build议 。

头文件用于声明。

头文件为每一个直接或间接包含它们的.cpp文件编译一次,并且在main()之前的程序初始化时运行任何函数之外的代码。

通过: foo::i = VALUE; 到头文件中, foo:i将为每个.cpp文件分配值VALUE (不pipe是什么),并且在main()运行之前,这些赋值将以不确定的顺序(由链接器确定)发生。

如果我们将#define VALUE设置为我们的.cpp文件中的一个不同的数字,该怎么办? 它会编译好,我们将无法知道哪一个获胜,直到我们运行该程序。

永远不要把执行的代码放到头文件中,原因是你永远不会包含.cpp文件。

包括警卫(我同意你应该总是使用)来保护你不同的东西:同一个头文件在编译一个.cpp文件时间接地包含多次#include

使用Microsoft编译器[1],不是int静态variables也可以在头文件中定义,但在类声明之外,使用Microsoft特定的__declspec(selectany)

 class A { static B b; } __declspec(selectany) A::b; 

请注意,我并不是说这很好,我只是说可以做到。

[1]现在,比MSC更多的编译器支持__declspec(selectany) – 至lessgcc和clang。 也许更多。

 int foo::i = 0; 

是初始化variables的正确语法,但它必须在源文件(.cpp)而不是在标题中。

因为它是一个静态variables,编译器只需要创build它的一个副本。 你必须在你的代码中有一行“int foo:i”,告诉编译器把它放在哪里,否则你会得到一个链接错误。 如果是在头文件中,您将在包含头文件的每个文件中得到一个副本,所以从链接器中获取多个定义的符号错误。

我没有足够的代表来添加这个注释,但IMO最好用#include守卫编写头文件,正如Paranaix在几个小时前提到的那样,它将防止出现多重定义错误。 除非您已经使用单独的CPP文件,否则不需要仅使用一个来初始化静态非整数成员。

 #ifndef FOO_H #define FOO_H #include "bar.h" class foo { private: static bar i; }; bar foo::i = VALUE; #endif 

我看到没有必要为此使用单独的CPP文件。 当然,你可以,但没有技术上的原因,你应该不得不。

如果你想初始化一些复合types(festring),你可以这样做:

 class SomeClass { static std::list<string> _list; public: static const std::list<string>& getList() { struct Initializer { Initializer() { // Here you may want to put mutex _list.push_back("FIRST"); _list.push_back("SECOND"); .... } } static Initializer ListInitializationGuard; return _list; } }; 

由于ListInitializationGuardSomeClass::getList()方法中的一个静态variables,它将只被构造一次,这意味着构造函数被调用一次。 这将initialize _listvariables以便您需要的值。 随后对getList任何调用getList将简单地返回已经初始化的_list对象。

当然你必须通过调用getList()方法访问_list对象。

如果您使用标题保护,您也可以在头文件中包含分配。 我已经使用这个技术来创build一个C ++库。 实现相同结果的另一种方法是使用静态方法。 例如…

 class Foo { public: int GetMyStatic() const { return *MyStatic(); } private: static int* MyStatic() { static int mStatic = 0; return &mStatic; } } 

上面的代码具有不需要CPP /源文件的“奖励”。 再一次,我用我的C ++库的方法。

我遵循卡尔的想法。 我喜欢它,现在我也使用它。 我已经改变了一些符号并添加了一些function

 #include <stdio.h> class Foo { public: int GetMyStaticValue () const { return MyStatic(); } int & GetMyStaticVar () { return MyStatic(); } static bool isMyStatic (int & num) { return & num == & MyStatic(); } private: static int & MyStatic () { static int mStatic = 7; return mStatic; } }; int main (int, char **) { Foo obj; printf ("mystatic value %d\n", obj.GetMyStaticValue()); obj.GetMyStaticVar () = 3; printf ("mystatic value %d\n", obj.GetMyStaticValue()); int valMyS = obj.GetMyStaticVar (); int & iPtr1 = obj.GetMyStaticVar (); int & iPtr2 = valMyS; printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2)); } 

这个输出

 mystatic value 7 mystatic value 3 is my static 1 0 

也在privateStatic.cpp文件中工作:

 #include <iostream> using namespace std; class A { private: static int v; }; int A::v = 10; // possible initializing int main() { A a; //cout << A::v << endl; // no access because of private scope return 0; } // g++ privateStatic.cpp -o privateStatic && ./privateStatic 

怎么样一个set_default()方法?

 class foo { public: static void set_default(int); private: static int i; }; void foo::set_default(int x) { i = x; } 

我们只需要使用set_default(int x)方法,我们的staticvariables就会被初始化。

这与其余的评论不会有分歧,实际上它遵循了在全局范围内初始化variables的相同原理,但是通过使用这个方法,我们使得它明确(而且易于理解)而不是定义的variables挂在那里。

从C ++ 17开始,静态成员可以用inline关键字在头文件中定义。

http://en.cppreference.com/w/cpp/language/static

“一个静态数据成员可以声明为内联,一个内联的静态数据成员可以在类定义中定义,并且可以指定一个默认的成员初始化器,它不需要超类定义:”

 struct X { inline static int n = 1; }; 

这是否符合你的目的?

 //header file struct MyStruct { public: const std::unordered_map<std::string, uint32_t> str_to_int{ { "a", 1 }, { "b", 2 }, ... { "z", 26 } }; const std::unordered_map<int , std::string> int_to_str{ { 1, "a" }, { 2, "b" }, ... { 26, "z" } }; std::string some_string = "justanotherstring"; uint32_t some_int = 42; static MyStruct & Singleton() { static MyStruct instance; return instance; } private: MyStruct() {}; }; //Usage in cpp file int main(){ std::cout<<MyStruct::Singleton().some_string<<std::endl; std::cout<<MyStruct::Singleton().some_int<<std::endl; return 0; } 

当我第一次遇到这个问题时,我只想提一些有点奇怪的东西。

我需要在模板类中初始化一个私有静态数据成员。

在.h或.hpp中,它看起来像这样来初始化一个模板类的静态数据成员:

 template<typename T> Type ClassName<T>::dataMemberName = initialValue; 

您遇到的链接器问题可能是由以下原因造成的:

  • 在头文件中提供类和静态成员定义,
  • 在两个或多个源文件中包含此头文件。

对于那些以C ++开始的人来说,这是一个常见的问题。 静态类成员必须在单个转换单元中初始化,即在单个源文件中。

不幸的是,静态类成员必须在类体外进行初始化。 这使得编写只有头文件的代码变得复杂,因此,我使用的方法非常不同。 您可以通过静态或非静态类函数提供您的静态对象,例如:

 class Foo { // int& getObjectInstance() const { static int& getObjectInstance() { static int object; return object; } void func() { int &object = getValueInstance(); object += 5; } };