如何初始化的私人静成员在C++?
-
06-07-2019 - |
题
什么是最好的方式来初始化的一个私人、静态数据成员在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;
如果初始化在头文件中,那么包含头文件的每个文件都将具有静态成员的定义。因此,在链接阶段,您将获得链接器错误,因为初始化变量的代码将在多个源文件中定义。
注意: Matt Curtis:指出如果静态成员变量是const int类型,C ++允许简化上述内容(例如 int
, bool
, char
)。然后,您可以直接在头文件中的类声明中声明和初始化成员变量:
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;
};
对于此问题的未来观众,我想指出您应该避免 monkey0506建议。
头文件用于声明。
头文件为每个直接或间接 #includes
的 .cpp
文件编译一次,任何函数之外的代码在程序初始化时运行,在 main()的代码>
通过将 foo :: i = VALUE;
放入标题, foo:i
将被赋值 VALUE
(无论如何is)对于每个 .cpp
文件,这些赋值将在 main()
运行之前以不确定的顺序(由链接器确定)发生。
如果我们 #define VALUE
是我们的 .cpp
文件中的其他数字怎么办?它会编译得很好,在我们运行程序之前,我们无法知道哪一个获胜。
永远不要将已执行的代码放入标题中,原因与您从未 #include
.cpp
文件的原因相同。
包括警卫(我同意你应该经常使用)保护你免受不同的事情:在编译单个 .cpp
时多次间接 #include
d的相同标题文件
从C ++ 17开始,可以在标题中使用内嵌关键字定义静态成员。
http://en.cppreference.com/w/cpp/language/static
"静态数据成员可以内联声明。可以在类定义中定义内联静态数据成员,并可以指定默认成员初始值设定项。它不需要一个类外定义:“
struct X
{
inline static int n = 1;
};
使用Microsoft编译器[1],非 int
-like的静态变量也可以在头文件中定义,但在类声明之外,使用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 guard 无论如何,正如几小时前Paranaix所指出的那样可以防止出现多重定义错误。除非您已经使用单独的CPP文件,否则不必仅使用一个来初始化静态非整数成员。
#ifndef FOO_H
#define FOO_H
#include "bar.h"
class foo
{
private:
static bar i;
};
bar foo::i = VALUE;
#endif
我认为没有必要为此使用单独的CPP文件。当然,你可以,但没有技术原因你应该这么做。
如果你想初始化一些复合类型(f.e. string),你可以这样做:
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()
方法中的静态变量,它只会被构造一次,这意味着构造函数被调用一次。这将初始化_list
变量为您需要的值。对 getList
的任何后续调用都将返回已初始化的 _list
对象。
当然,您必须始终通过调用 getList()
方法访问 _list
对象。
如果使用标题保护,您还可以在标题文件中包含该分配。我已经将这种技术用于我创建的C ++库。实现相同结果的另一种方法是使用静态方法。例如......
class Foo
{
public:
int GetMyStatic() const
{
return *MyStatic();
}
private:
static int* MyStatic()
{
static int mStatic = 0;
return &mStatic;
}
}
上述代码具有“奖金”。不需要CPP /源文件。同样,我用于C ++库的方法。
我遵循卡尔的想法。我喜欢它,现在我也使用它。 我改变了一些符号并添加了一些功能
#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
适用于多个对象的静态构造函数模式
提出了一个成语: https://stackoverflow.com/a/27088552/895245 但这里有一个成语更清洁的版本,不需要为每个成员创建一个新方法,以及一个可运行的示例:
#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);
}
另请参阅: C ++中的静态构造函数?我需要初始化私有静态对象
使用 g ++ -std = c ++ 11 -Wall -Wextra
,GCC 7.3,Ubuntu 18.04进行测试。
还在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
变量就会被初始化。
这不会与其他评论产生分歧,实际上它遵循在全局范围内初始化变量的相同原则,但是通过使用此方法,我们使其明确(并且易于理解)而不是将变量的定义挂在那里。
连接问题,你遇到的是可能引起的:
- 提供这两类和静态部件的定义在标题文件,
- 包括这个标题中的两个或更多的来源的文件。
这是一个常见的问题,对于那些启用C++。静类成员必须进行初始化在单一的翻译单位即在单一来源文件。
不幸的是,类的静态部件必须进行初始化之外的类的身体。这种复杂写入标题仅代码,因此,我使用的是相当不同的方法。你可以提供静态目通过静态或非静态的类功能,例如:
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
当我第一次遇到这个时,我只是想提一些有点奇怪的东西。
我需要在模板类中初始化私有静态数据成员。
<。>在.h或.hpp中,它看起来像这样初始化模板类的静态数据成员:template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
这是否符合您的目的?
//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;
}