关于初始化:如何初始化C++中的私有静态成员?

How to initialize private static members in C++?

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

1
2
3
4
5
6
7
class foo
{
    private:
        static int i;
};

int foo::i = 0;

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


类声明应该在头文件中(如果不共享,则在源文件中)。文件:Fo.h

1
2
3
4
5
class foo
{
    private:
        static int i;
};

但是初始化应该在源文件中。文件:英尺.CPP

1
int foo::i = 0;

如果初始化在头文件中,则包含头文件的每个文件都将具有静态成员的定义。因此,在链接阶段,您将得到链接器错误,因为初始化变量的代码将在多个源文件中定义。

注:Matt Curtis指出,如果静态成员变量为const int类型(例如EDCOX1,0,EDCOX1,1,EDCOX1,2),则C++允许简化上述。然后可以直接在头文件中的类声明内声明和初始化成员变量:

1
2
3
4
5
class foo
{
    private:
        static int const i = 42;
};


变量:

福:

1
2
3
4
5
class foo
{
private:
    static int i;
};

英尺:CPP:

1
int foo::i = 0;

这是因为在您的程序中只能有一个foo::i的实例。它类似于头文件中的extern int i,源文件中的int i

对于常量,可以在类声明中直接输入值:

1
2
3
4
5
6
class foo
{
private:
    static int i;
    const static int a = 42;
};


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

头文件用于声明。

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

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

如果我们的#define VALUE在一个.cpp文件中是一个不同的数字呢?它编译得很好,在运行程序之前,我们无法知道谁会赢。

不要将执行的代码放在头文件中,原因与不使用#include文件相同。

包括保护装置(我同意你应该经常使用)保护你不受其他东西的影响:在编译单个.cpp文件时,同一个头文件被多次间接地#included


由于C++ 17,静态成员可以在内联关键字中定义。

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

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

1
2
3
4
struct X
{
    inline static int n = 1;
};


使用Microsoft编译器[1],也可以使用特定于Microsoft的EDOCX1[7]在头文件中定义非EDOCX1[0]类型的静态变量,但不在类声明的范围内。

1
2
3
4
5
6
class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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


1
int foo::i = 0;

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

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


我在这里没有足够的代表来添加这个评论,但是我认为用include guards来编写头部是一种很好的方式,正如几个小时前paranaix指出的那样,这可以防止多重定义错误。除非您已经在使用一个单独的cpp文件,否则不必只使用一个文件来初始化静态非整型成员。

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef FOO_H
#define FOO_H
#include"bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

我认为没有必要为此使用单独的cpp文件。当然,你可以,但是没有技术上的原因让你不得不这么做。


如果要初始化某个复合类型(F.E.字符串),可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()方法中的静态变量,因此它只构造一次,这意味着构造函数被调用一次。这将使ecx1(5)变量变为您需要的值。对getList的任何后续调用都只返回已经初始化的_list对象。

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


如果使用头保护,也可以在头文件中包含分配。我已经用这个技术创建了一个C++库。实现相同结果的另一种方法是使用静态方法。例如。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

上述代码的"好处"是不需要cpp/源文件。再次,我使用的方法用于我的C++库。


我听从卡尔的意见。我喜欢它,现在我也用它。我改变了一点符号,增加了一些功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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
"
, obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d
"
, obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d
"
, Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

这个输出

1
2
3
mystatic value 7
mystatic value 3
is my static 1 0

适用于多个对象的静态构造函数模式

一个习惯用法是在:https://stackoverflow.com/a/27088552/895245中提出的,但是这里有一个更清晰的版本,它不需要为每个成员创建新的方法,以及一个可运行的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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);
}

Github上游。

参见:C++中的静态构造函数?我需要初始化私有静态对象

使用g++ -std=c++11 -Wall -Wextra、GCC 7.3、Ubuntu 18.04进行测试。


一个set_default()方法怎么样?

1
2
3
4
5
6
7
8
9
10
11
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变量将被初始化。

这与注释的其他部分没有什么不同,实际上它遵循了在全局范围内初始化变量的相同原则,但是通过使用此方法,我们使变量显式(并且易于理解)而不是将变量的定义挂在那里。


同时在privatestatic.cpp文件中工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

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

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

1
2
3
4
5
6
class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

这种方法不需要提供定义,也避免了使用常量lvalue,这样可以避免一些头痛,例如,当您不小心使用了它时。


当我第一次遇到这个的时候,我只想提一件有点奇怪的事情。

我需要初始化模板类中的私有静态数据成员。

在.h或.hpp中,初始化模板类的静态数据成员如下所示:

1
2
template<typename T>
Type ClassName<T>::dataMemberName = initialValue;


这符合你的目的吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//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;
}