在 C++ 中初始化私有静态数据成员的最佳方法是什么?我在我的头文件中尝试了这个,但它给了我奇怪的链接器错误:
class foo
{
private:
static int i;
};
int foo::i = 0;
我猜这是因为我无法从类外初始化私有成员。那么最好的方法是什么?
inline static int x[] = {1, 2, 3};
。请参阅en.cppreference.com/w/cpp/language/static#Static_data_members
类声明应该在头文件中(如果不共享,则在源文件中)。文件:foo.h
class foo
{
private:
static int i;
};
但是初始化应该在源文件中。文件:foo.cpp
int foo::i = 0;
如果初始化在头文件中,则包含头文件的每个文件都将具有静态成员的定义。因此,在链接阶段,您将收到链接器错误,因为初始化变量的代码将在多个源文件中定义。 static int i
的初始化必须在任何函数之外完成。
注意: Matt Curtis:指出如果静态成员变量是 const integer type (bool
, char
, char8_t
[因为 C ++20]、char16_t
、char32_t
、wchar_t
、short
、int
、long
、long long
或任何实现定义的扩展整数类型,包括任何有符号、无符号和 cv -合格的变体。)。然后,您可以直接在头文件的类声明中声明和初始化成员变量:
class foo
{
private:
static int const i = 42;
};
对于变量:
富.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;
};
private
变量可以在 Class 之外在这里初始化,这也可以用于非静态变量。
Class
之外的非静态私有变量初始化在 Cpp 中没有任何意义。
从 C++17 开始,静态成员可以使用 inline 关键字在标头中定义。
http://en.cppreference.com/w/cpp/language/static
“静态数据成员可以内联声明。内联静态数据成员可以在类定义中定义,并且可以指定默认成员初始化程序。它不需要类外定义:”
struct X
{
inline static int n = 1;
};
对于这个问题的未来观众,我想指出您应该避免monkey0506 is suggesting。
头文件用于声明。
对于直接或间接#includes
它们的每个 .cpp
文件,头文件都会编译一次,并且任何函数之外的代码在程序初始化时运行,main()
之前。
通过将: foo::i = VALUE;
放入标题中,将为每个 .cpp
文件分配 VALUE
值(无论是什么),并且这些分配将在 { 之前以不确定的顺序(由链接器确定)发生5}运行。
如果我们的 .cpp
文件之一中的 #define VALUE
是不同的数字怎么办?它将编译得很好,在我们运行程序之前,我们无法知道哪个获胜。
永远不要将执行的代码放入标头中,原因与您从不#include
.cpp
文件的原因相同。
包含防护(我同意您应该始终使用)保护您免受不同的影响:在编译单个 .cpp
文件时,同一个标头被间接 #include
d 多次。
使用 Microsoft 编译器 [1],与 int
不同的静态变量也可以在头文件中定义,但在类声明之外,使用 Microsoft 特定的 __declspec(selectany)
。
class A
{
static B b;
}
__declspec(selectany) A::b;
请注意,我并不是说这很好,我只是说可以做到。
[1] 如今,支持 __declspec(selectany)
的编译器比 MSC 多 - 至少 gcc 和 clang。也许更多。
int foo::i = 0;
是初始化变量的正确语法,但它必须放在源文件 (.cpp) 中而不是头文件中。
因为它是一个静态变量,编译器只需要创建它的一个副本。您必须在代码中的某个位置有一行“int foo:i”来告诉编译器将它放在哪里,否则会出现链接错误。如果它在标题中,您将在每个包含标题的文件中获得一个副本,因此从链接器获取多重定义的符号错误。
如果你想初始化一些复合类型(fe 字符串),你可以这样做:
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()
方法中的静态变量,因此它只会被构造一次,这意味着构造函数被调用一次。这将 initialize _list
变量设置为您需要的值。对 getList
的任何后续调用都将简单地返回已初始化的 _list
对象。
当然,您必须始终通过调用 getList()
方法来访问 _list
对象。
适用于多个对象的 C++11 静态构造函数模式
在 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);
}
编译并运行:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
另请参阅:static constructors in C++? I need to initialize private static objects
在 Ubuntu 19.04 上测试。
C++17 内联变量
在:https://stackoverflow.com/a/45062055/895245 中提到,但这里有一个多文件可运行示例以使其更加清晰:How do inline variables work?
这个很棒的 C++17 特性使我们能够:
方便地为每个常量只使用一个内存地址
将其存储为 constexpr:如何声明 constexpr extern?
从一个标题在一行中执行
主文件
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
不是main.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
非main.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
编译并运行:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
我没有足够的代表在这里将其添加为评论,但是 IMO 无论如何用 #include guards 编写标题是一种很好的风格,正如 Paranaix 几小时前所指出的那样,这可以防止多定义错误。除非您已经在使用单独的 CPP 文件,否则没有必要仅使用一个来初始化静态非整数成员。
#ifndef FOO_H
#define FOO_H
#include "bar.h"
class foo
{
private:
static bar i;
};
bar foo::i = VALUE;
#endif
我认为不需要为此使用单独的 CPP 文件。当然,你可以,但没有技术上的理由你必须这样做。
#endif // FOO_H
#pragma once
#pragma once
是通过翻译单元对多个定义的解决方案,但它应该是最终的、不得已的解决方案,而不是一个人的编码风格......
如果您使用标头保护,您还可以在头文件中包含分配。我已将这种技术用于我创建的 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
您遇到的链接器问题可能是由以下原因引起的:
在头文件中提供类和静态成员定义,
在两个或多个源文件中包含此标头。
对于那些从 C++ 开始的人来说,这是一个常见的问题。静态类成员必须在单个翻译单元中初始化,即在单个源文件中。
不幸的是,静态类成员必须在类主体之外进行初始化。这使编写仅标头代码变得复杂,因此,我使用了完全不同的方法。您可以通过静态或非静态类函数提供静态对象,例如:
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
也在 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
变量就会被初始化。
这不会与其他评论意见相左,实际上它遵循在全局范围内初始化变量的相同原则,但是通过使用这种方法,我们使其明确(并且易于理解)而不是定义的变量挂在那里。
以下是一个简单示例中的所有可能性和错误...
#ifndef Foo_h
#define Foo_h
class Foo
{
static const int a = 42; // OK
static const int b {7}; // OK
//static int x = 42; // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
//static int y {7}; // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
static int x;
static int y;
int m = 42;
int n {7};
};
// Foo::x = 42; // error: 'int Foo::x' is private
int Foo::x = 42; // OK in Foo.h if included in only one *.cpp -> *.o file!
int Foo::y {7}; // OK
// int Foo::y {7}; // error: redefinition of 'int Foo::y'
// ONLY if the compiler can see both declarations at the same time it,
// OTHERWISE you get a linker error
#endif // Foo_h
但最好把它放在 Foo.cpp 中。这样您可以单独编译每个文件并稍后链接它们,否则 Foo:x 将出现在多个目标文件中并导致链接器错误。 ...
// Foo::x = 42; // error: 'int Foo::x' is private, bad if Foo::X is public!
int Foo::x = 42; // OK in Foo.h if included in only one *.cpp -> *.o file!
int Foo::y {7}; // OK
当我第一次遇到这个时,我只是想提一些对我来说有点奇怪的事情。
我需要在模板类中初始化一个私有静态数据成员。
在 .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;
}