初始化私有静态成员
在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(例如int
, bool
, char
),那么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; } };
由于ListInitializationGuard
是SomeClass::getList()
方法中的一个静态variables,它将只被构造一次,这意味着构造函数被调用一次。 这将initialize _list
variables以便您需要的值。 随后对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)
方法,我们的static
variables就会被初始化。
这与其余的评论不会有分歧,实际上它遵循了在全局范围内初始化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; } };