如何在c++中初始化私有静态成员?

在c++中初始化私有静态数据成员的最佳方法是什么?我在头文件中尝试了这一点,但它给了我奇怪的链接器错误:

class foo
{
private:
static int i;
};


int foo::i = 0;

我猜这是因为我不能从类外部初始化一个私有成员。那么最好的方法是什么呢?

622604 次浏览

类声明应该在头文件中(如果不共享则在源文件中) 文件:foo。< / p >

class foo
{
private:
static int i;
};
但是初始化应该在源文件中 文件:foo.cpp < / p >
int foo::i = 0;
如果初始化是在头文件中,那么每个包含头文件的文件都有一个静态成员的定义。因此,在链接阶段,你会得到链接器错误,因为初始化变量的代码将在多个源文件中定义。 static int i的初始化必须在任何函数之外完成

Matt Curtis:指出,如果静态成员变量是char1 (char2), c++允许对上述内容进行简化。然后你可以直接在头文件的类声明中声明和初始化成员变量:

class foo
{
private:
static int const i = 42;
};

对于变量:

foo。:

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;
};
int foo::i = 0;

是初始化变量的正确语法,但它必须放在源文件(.cpp)中,而不是放在头文件中。

因为它是一个静态变量,所以编译器只需要创建它的一个副本。你必须在你的代码中有一行"int foo:i"来告诉编译器把它放在哪里,否则你会得到一个链接错误。如果这是在一个头,你会得到一个拷贝在每个文件,包括头,所以从链接器获得多重定义的符号错误。

使用Microsoft编译器[1],与# eyz0不同的静态变量也可以在头文件中定义,但可以在类声明之外定义,使用Microsoft特定的__declspec(selectany)

class A
{
static B b;
}


__declspec(selectany) A::b;

请注意,我并不是说这是好的,我只是说这是可以做到的。

现在,支持__declspec(selectany)的编译器比MSC多——至少gcc和clang。甚至更多。

我在这里没有足够的代表来添加这一点作为注释,但在我看来,使用# include警卫编写头文件是很好的风格,正如几小时前由Paranaix指出的那样,这可以防止多重定义错误。除非已经使用了单独的CPP文件,否则没有必要只使用一个文件来初始化静态的非整型成员。

#ifndef FOO_H
#define FOO_H
#include "bar.h"


class foo
{
private:
static bar i;
};


bar foo::i = VALUE;
#endif

我认为没有必要为此使用单独的CPP文件。当然,你可以这样做,但是没有技术上的理由必须这样做。

对于这个问题的未来观众,我想指出您应该避免Monkey0506建议

头文件用于声明。

头文件直接或间接地为每个.cpp文件编译一次,任何函数之外的代码在程序初始化时运行,在main()之前。

通过将:foo::i = VALUE;放入头文件中,foo:i将为每个.cpp文件分配值VALUE(不管那是什么),并且这些分配将在main()运行之前以不确定的顺序发生(由链接器决定)。

如果我们#define VALUE是一个不同的数字在我们的.cpp文件?它将编译良好,我们将无法知道哪个胜出,直到我们运行程序。

永远不要把执行的代码放在头文件中,就像永远不要把#include文件变成.cpp文件一样。

包括守卫(我同意你应该经常使用)保护你免受一些不同的事情:在编译单个.cpp文件时,同一个头文件间接地多次#included。

如果使用头保护,也可以在头文件中包含赋值。我在自己创建的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

也在privatstatic .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变量将被初始化。

这与其他注释并不矛盾,实际上它遵循了在全局作用域中初始化变量的相同原则,但是通过使用这个方法,我们使其显式(并且易于看到-理解),而不是将变量的定义挂在那里。

如果你想初始化一些复合类型(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;
}
};

由于ListInitializationGuardSomeClass::getList()方法中的静态变量,它将只被构造一次,这意味着构造函数将被调用一次。这将initialize _list变量为您需要的值。对getList的任何后续调用都将返回已经初始化的_list对象。

当然,您必须始终通过调用getList()方法来访问_list对象。

这符合你的目的吗?

//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++ 17开始,静态成员可以在头文件中用内联关键字定义。

http://en.cppreference.com/w/cpp/language/static

静态数据成员可以内联声明。内联静态数据成员可以在类定义中定义,并且可以指定默认成员初始化式。它不需要一个类外定义:

struct X
{
inline static int n = 1;
};

您遇到的链接器问题可能是由以下原因引起的:

  • 在头文件中提供类和静态成员定义,
  • 在两个或多个源文件中包含此头文件。

对于那些从c++开始学习的人来说,这是一个常见的问题。静态类成员必须在单个翻译单元中初始化,即在单个源文件中初始化。

不幸的是,静态类成员必须在类体之外初始化。这使得只写头的代码变得复杂,因此,我使用了完全不同的方法。你可以通过静态或非静态类函数来提供你的静态对象,例如:

class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}


void func() {
int &object = getValueInstance();
object += 5;
}
};

c++ 11静态构造函数模式,适用于多个对象

https://stackoverflow.com/a/27088552/895245上提出了一个习惯用法,但这里有一个更简洁的版本,不需要为每个成员创建一个新方法。

main.cpp

#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);
}

# EYZ0。

编译并运行:

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++ 17特性允许我们:

main.cpp

#include <cassert>


#include "notmain.hpp"


int main() {
// Both files see the same memory address.
assert(&notmain_i == notmain_func());
assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP


inline constexpr int notmain_i = 42;


const int* notmain_func();


#endif

notmain.cpp

#include "notmain.hpp"


const int* notmain_func() {
return &notmain_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

# EYZ0。

定义常量的一种“老派”方法是用enum替换它们:

class foo
{
private:
enum {i = 0}; // default type = int
enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

这种方法不需要提供定义,并避免使常量左值,这可以为你省去一些麻烦,例如当你不小心ODR-use它。

以下是一个简单例子中的所有可能性和错误……

#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