在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;
如果初始化在头文件中,那么包含头文件的每个文件都将具有静态成员的定义.因此,在链接阶段,您将获得链接器错误,因为初始化变量的代码将在多个源文件中定义.
注意:马特柯蒂斯:指出C++允许上述的简化如果静态成员变量是const int的类型(例如static int i
,int
,bool
).然后,您可以直接在头文件中的类声明中声明和初始化成员变量:
class foo { private: static int const i = 42; };
对于变量:
foo.h中:
class foo { private: static int i; };
Foo.cpp中:
int foo::i = 0;
这是因为程序中只能有一个实例foo::i
.它类似于extern int i
头文件和int i
源文件中的等价物.
对于常量,您可以将值直接放在类声明中:
class foo { private: static int i; const static int a = 42; };
从C++ 17开始,可以在标题中使用inline关键字定义静态成员.
http://en.cppreference.com/w/cpp/language/static
"可以内联声明静态数据成员.可以在类定义中定义内联静态数据成员,并可以指定默认成员初始化程序.它不需要类外定义:"
struct X { inline static int n = 1; };
对于这个问题的未来观众,我想指出你应该避免monkey0506的建议.
头文件用于声明.
头文件为每个.cpp
直接或间接文件编译一次#includes
,并且任何函数之外的代码在程序初始化之前运行main()
.
通过将:foo::i = VALUE;
放入标题,foo:i
将为VALUE
每个.cpp
文件分配值(无论是什么),并且这些分配将在main()
运行之前以不确定的顺序(由链接器确定)发生.
如果我们#define VALUE
在其中一个.cpp
文件中使用不同的数字怎么办?它编译得很好,在我们运行程序之前,我们无法知道哪一个获胜.
永远不要将已执行的代码放入标题中,原因与您从不#include
使用.cpp
文件相同.
包括警卫(我同意你应该总是使用它)保护你免受不同的事情:#include
编译单个.cpp
文件时间接d多次相同的标题
使用Microsoft编译器[1],非类似的静态变量int
也可以在头文件中定义,但在类声明之外,使用Microsoft特定的__declspec(selectany)
.
class A { static B b; } __declspec(selectany) A::b;
请注意,我并不是说这很好,我只是说可以做到.
[1]这些天,编译器比MSC支持更多__declspec(selectany)
- 至少是gcc和clang.也许更多.
int foo::i = 0;
是初始化变量的正确语法,但它必须放在源文件(.cpp)中而不是标题中.
因为它是一个静态变量,所以编译器只需要创建一个副本.你必须有一行"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文件.当然,你可以,但没有技术原因你应该这么做.
如果你想初始化一些复合类型(fe string)你可以做类似的事情:
class SomeClass { static std::list_list; public: static const std::list & 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
是一个静态变量inform SomeClass::getList()
方法,它只会被构造一次,这意味着构造函数被调用一次.这将initialize _list
根据您的需要变化.任何后续调用都getList
将只返回已初始化的_list
对象.
当然,您必须_list
始终通过调用getList()
方法来访问对象.
适用于多个对象的C ++ 11静态构造函数模式
提出了一个惯用法,网址为:https : //stackoverflow.com/a/27088552/895245,但是此处提供了一种更干净的版本,不需要为每个成员创建新方法。
main.cpp
#include#include // Normally on the .hpp file. class MyClass { public: static std::vector 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 MyClass::v; std::vector 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上游。
编译并运行:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp ./main.out
另请参见:C ++中的静态构造函数?我需要初始化私有静态对象
在Ubuntu 19.04上测试。
C ++ 17内联变量
在以下位置提及:https : //stackoverflow.com/a/45062055/895245,但这是一个可运行的多文件示例,以使其更清晰:内联变量如何工作?
如果使用标题保护,还可以在头文件中包含赋值.我已经将这种技术用于我创建的C++库.实现相同结果的另一种方法是使用静态方法.例如...
class Foo { public: int GetMyStatic() const { return *MyStatic(); } private: static int* MyStatic() { static int mStatic = 0; return &mStatic; } }
上面的代码具有不需要CPP /源文件的"奖励".同样,我用于我的C++库的方法.