How to initialize private static members in C++?
在C++中初始化私有、静态数据成员的最佳方法是什么?我在我的头文件中尝试了这个,但它给了我奇怪的链接器错误:
1 2 3 4 5 6 7 | class foo { private: static int i; }; int foo::i = 0; |
我猜这是因为我无法从类外部初始化私有成员。那么,最好的方法是什么呢?
类声明应该在头文件中(如果不共享,则在源文件中)。文件:Fo.h
1 2 3 4 5 | class foo { private: static int i; }; |
但是初始化应该在源文件中。文件:英尺.CPP
1 | int foo::i = 0; |
如果初始化在头文件中,则包含头文件的每个文件都将具有静态成员的定义。因此,在链接阶段,您将得到链接器错误,因为初始化变量的代码将在多个源文件中定义。
注:Matt Curtis指出,如果静态成员变量为const int类型(例如EDCOX1,0,EDCOX1,1,EDCOX1,2),则C++允许简化上述。然后可以直接在头文件中的类声明内声明和初始化成员变量:
1 2 3 4 5 | class foo { private: static int const i = 42; }; |
变量:
福:
1 2 3 4 5 | class foo { private: static int i; }; |
英尺:CPP:
1 | int foo::i = 0; |
这是因为在您的程序中只能有一个
对于常量,可以在类声明中直接输入值:
1 2 3 4 5 6 | class foo { private: static int i; const static int a = 42; }; |
对于这个问题的未来观众,我想指出的是,你应该避免Monkey0506的建议。
头文件用于声明。
对于直接或间接
通过将:
如果我们的
不要将执行的代码放在头文件中,原因与不使用
包括保护装置(我同意你应该经常使用)保护你不受其他东西的影响:在编译单个
由于C++ 17,静态成员可以在内联关键字中定义。
http://en.cppreference.com/w/cpp/language/static/静态
"静态数据成员可以内联声明。可以在类定义中定义内联静态数据成员,并且可以指定默认成员初始值设定项。它不需要类外定义:"
1 2 3 4 | struct X { inline static int n = 1; }; |
使用Microsoft编译器[1],也可以使用特定于Microsoft的EDOCX1[7]在头文件中定义非EDOCX1[0]类型的静态变量,但不在类声明的范围内。
1 2 3 4 5 6 | class A { static B b; } __declspec(selectany) A::b; |
注意,我不是说这是好的,我只是说这是可以做到的。
[1]现在,比MSC更多的编译器支持
1 | int foo::i = 0; |
是初始化变量的正确语法,但它必须进入源文件(.cpp)而不是头文件。
因为它是一个静态变量,所以编译器只需要创建它的一个副本。你必须有一行"int foo:i"来告诉编译器把它放在哪里,否则你会得到一个链接错误。如果它在一个头文件中,那么您将在包含该头文件的每个文件中获得一个副本,因此从链接器中获取多个定义的符号错误。
我在这里没有足够的代表来添加这个评论,但是我认为用include guards来编写头部是一种很好的方式,正如几个小时前paranaix指出的那样,这可以防止多重定义错误。除非您已经在使用一个单独的cpp文件,否则不必只使用一个文件来初始化静态非整型成员。
1 2 3 4 5 6 7 8 9 10 11 12 | #ifndef FOO_H #define FOO_H #include"bar.h" class foo { private: static bar i; }; bar foo::i = VALUE; #endif |
我认为没有必要为此使用单独的cpp文件。当然,你可以,但是没有技术上的原因让你不得不这么做。
如果要初始化某个复合类型(F.E.字符串),可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } }; |
由于
当然,您必须始终通过调用
如果使用头保护,也可以在头文件中包含分配。我已经用这个技术创建了一个C++库。实现相同结果的另一种方法是使用静态方法。例如。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Foo { public: int GetMyStatic() const { return *MyStatic(); } private: static int* MyStatic() { static int mStatic = 0; return &mStatic; } } |
上述代码的"好处"是不需要cpp/源文件。再次,我使用的方法用于我的C++库。
我听从卡尔的意见。我喜欢它,现在我也用它。我改变了一点符号,增加了一些功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #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 ", obj.GetMyStaticValue()); obj.GetMyStaticVar () = 3; printf ("mystatic value %d ", obj.GetMyStaticValue()); int valMyS = obj.GetMyStaticVar (); int & iPtr1 = obj.GetMyStaticVar (); int & iPtr2 = valMyS; printf ("is my static %d %d ", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2)); } |
这个输出
1 2 3 | mystatic value 7 mystatic value 3 is my static 1 0 |
适用于多个对象的静态构造函数模式
一个习惯用法是在:https://stackoverflow.com/a/27088552/895245中提出的,但是这里有一个更清晰的版本,它不需要为每个成员创建新的方法,以及一个可运行的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <cassert> #include <vector> // Normally on the .hpp file. class MyClass { public: static std::vector<int> v, v2; static struct _StaticConstructor { _StaticConstructor() { v.push_back(1); v.push_back(2); v2.push_back(3); v2.push_back(4); } } _staticConstructor; }; // Normally on the .cpp file. std::vector<int> MyClass::v; std::vector<int> MyClass::v2; // Must come after every static member. MyClass::_StaticConstructor MyClass::_staticConstructor; int main() { assert(MyClass::v[0] == 1); assert(MyClass::v[1] == 2); assert(MyClass::v2[0] == 3); assert(MyClass::v2[1] == 4); } |
Github上游。
参见:C++中的静态构造函数?我需要初始化私有静态对象
使用
一个
1 2 3 4 5 6 7 8 9 10 11 | class foo { public: static void set_default(int); private: static int i; }; void foo::set_default(int x) { i = x; } |
我们只需要使用
这与注释的其他部分没有什么不同,实际上它遵循了在全局范围内初始化变量的相同原则,但是通过使用此方法,我们使变量显式(并且易于理解)而不是将变量的定义挂在那里。
同时在privatestatic.cpp文件中工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #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 |
您遇到的链接器问题可能是由以下原因引起的:
- 在头文件中同时提供类和静态成员定义,
- 将此头文件包含在两个或多个源文件中。
对于那些从C++开始的人来说,这是一个常见的问题。静态类成员必须在单个翻译单元中初始化,即在单个源文件中初始化。
不幸的是,静态类成员必须在类体外部初始化。这使得只写头代码变得复杂,因此,我使用了完全不同的方法。可以通过静态或非静态类函数提供静态对象,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Foo { // int& getObjectInstance() const { static int& getObjectInstance() { static int object; return object; } void func() { int &object = getValueInstance(); object += 5; } }; |
定义常量的一种"老派"方法是用
1 2 3 4 5 6 | class foo { private: enum {i = 0}; // default type = int enum: int64_t {HUGE = 1000000000000}; // may specify another type }; |
这种方法不需要提供定义,也避免了使用常量lvalue,这样可以避免一些头痛,例如,当您不小心使用了它时。
当我第一次遇到这个的时候,我只想提一件有点奇怪的事情。
我需要初始化模板类中的私有静态数据成员。
在.h或.hpp中,初始化模板类的静态数据成员如下所示:
1 2 | template<typename T> Type ClassName<T>::dataMemberName = initialValue; |
这符合你的目的吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //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; } |