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


518

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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


2
嗨杰森。我没有发现有关静态成员(尤其是整数成员)的默认初始化的评论。实际上,您需要编写int foo :: i以便链接程序可以找到它,但是它将自动用0初始化!这行就足够了:int foo :: i; (这对于存储在静态存储器中的所有对象均有效,链接器负责初始化静态对象。)
Nico 2012年

1
以下答案不适用于模板类。他们说:初始化必须进入源文件。对于模板类,这既不可能,也没有必要。
约阿希姆·W

7
C ++ 17允许静态数据成员(即使对于非整数类型)的内联初始化:inline static int x[] = {1, 2, 3};。参见en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Answers:


556

类声明应位于头文件中(如果未共享,则应位于源文件中)。
档案:foo.h

class foo
{
    private:
        static int i;
};

但是初始化应该在源文件中。
档案:foo.cpp

int foo::i = 0;

如果初始化在头文件中,则每个包含头文件的文件都将具有静态成员的定义。因此,在链接阶段,您将得到链接器错误,因为初始化变量的代码将在多个源文件中定义。static int i必须在任何功能之外进行初始化。

注意:马特柯蒂斯:指出C ++允许的简化上述如果静态成员变量是const int的类型(例如intboolchar)。然后,您可以直接在头文件的类声明中声明和初始化成员变量:

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

4
是。但是我认为这个问题已经简化了。从技术上讲,声明和定义都可以在一个源文件中。但这又限制了其他类对类的使用。
马丁·约克

11
实际上不仅仅是POD,它还必须是一个int类型(int,short,bool,char ...)
Matt Curtis

9
请注意,这不仅是如何初始化值的问题:像这样定义的const整数类型可以通过实现转换为编译时间常数。这并不总是您想要的,因为它会增加二进制依赖性:如果值更改,则客户端代码需要重新编译。
史蒂夫·杰索普

5
@Martin:除了更正s / POD / integral type /外,如果使用了地址,则还需要定义。听起来很奇怪,但在类定义中带有初始化器的声明不是定义。该模板常量成语提供了你需要在头文件中定义的情况下,一种解决方法。另一个更简单的解决方法是产生局部静态常数值的函数。干杯&心连心,
欢呼和心连心。-Alf 2010年

3
您可以添加一个说明,即int foo :: i = 0; 不应在函数内部(包括main函数)。我在主要功能的开始就拥有了它,但它不是那样的。
qwerty9967

89

对于变量

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

2
这是有道理的。我也将添加我的解释。但应注意,这仅适用于POD类型。
马丁·约克

从那时起,C ++允许在类中进行声明就很好,而对于整数类型则没有定义。由于C ++ 98本身或C ++ 03或何时开始?请分享真实的链接。C ++标准措辞与编译器不同步。他们提到,如果使用成员,则仍应定义成员。因此,我不需要C ++ Standard引用
smRaj 2014年

1
我想知道为什么private变量可以在Class外部进行初始化,这是否也可以用于非静态变量。
克里希纳(Krishna Oza)2015年

找到解释了吗?@Krishna_Oza
nn0p

@ nn0p还没有,但是外部的非静态私有变量初始化Class在Cpp中没有任何意义。
克里希纳(Krishna Oza)

41

从C ++ 17开始,可以在标题中使用inline关键字定义静态成员。

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

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

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

1
从C ++ 17开始,这是可能的,而C ++ 17目前正在成为新标准。
Grebu

31

对于以后这个问题的观众,我想指出,您应该避免monkey0506的建议

头文件用于声明。

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

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

如果我们#define VALUE在其中一个.cpp文件中使用其他号码怎么办?它将编译良好,并且在运行该程序之前,我们将无法知道哪一个获胜。

永远不要将执行后的代码放入标头中,其原因与从未使用#include.cpp文件的原因相同。

包含卫士(我同意您应该始终使用)来保护您免受不同的伤害:同一头文件#include在编译单个.cpp文件时被多次间接d


2
当然,您对此是正确的,但在类模板的情况下(这没有问,但我碰巧要处理很多事情)。因此,如果完全定义了类而不是类模板,则将这些静态成员放在单独的CPP文件中,但是对于类模板,定义必须位于同一转换单元(例如,头文件)中。
monkey0506

@ monkey_05_06:这似乎是避免在模板代码中使用静态成员的参数:对于类的每个实例化,您已经最终拥有一个静态成员。通过将标头编译到多个cpp文件中,问题变得更糟了……您可能会得到许多冲突的定义。
约书亚·克莱顿

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/…此链接描述了如何在主要功能中实例化静态模板成员,如果有一点负担,它会更简洁。
约书亚·克莱顿

1
您的论点确实很大。首先,您无法#define VALUE,因为宏名称ot是有效的标识符。即使您可以-谁会这样做?头文件用于声明-?来吧..唯一应避免在标头中放置值的情况是与使用过的东西作斗争。每当需要更改值时,将值放在标头中都可能导致不必要的重新编译。
Aleksander Fular

20

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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


17
int foo::i = 0; 

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

因为它是一个静态变量,所以编译器只需要创建一个副本。您必须在代码中的某些位置插入“ int foo:i”行,以告诉编译器将其放置在哪里,否则会出现链接错误。如果在标头中,则将在每个包含标头的文件中获得一个副本,因此从链接器获取多个已定义的符号错误。


12

我在这里没有足够的代表将该注释添加为注释,但是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文件。当然可以,但是没有技术理由必须这样做。


21
#include卫队只是防止每个翻译单元有多个定义。
Paul Fultz II

3
关于好风格:您应该在结尾处添加注释:#endif // FOO_H
里加

9
仅当只有一个包含foo.h的编译单元时,此方法才有效。如果两个或多个cpps包含foo.h,这是一种典型情况,则每个cpp都将声明相同的静态变量,因此,除非您对文件使用了包编译(编译,只有一个包含所有cpp的文件)。但是,尽管软件包编译很棒,但解决问题的方法是在cpp中声明(int foo :: i = 0;)!
Alejadro Xalabarder

1
或只使用#pragma once
tambre

12

如果要初始化某种复合类型(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对象。

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


1
这是该惯用法的一个版本,不需要为每个成员对象创建一个方法:stackoverflow.com/a/48337288/895245
Ciro Santilli郝海东冠状病六四事件法轮功

9

适用于多个对象的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);
}

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,但这是一个可运行的多文件示例,以使其更清晰:内联变量如何工作?


5

如果使用标题防护,也可以将分配包括在标题文件中。我将这种技术用于我创建的C ++库。获得相同结果的另一种方法是使用静态方法。例如...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

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

上面的代码具有不需要CPP /源文件的“好处”。同样,这是我用于C ++库的一种方法。


4

我遵循卡尔的想法。我喜欢它,现在也使用它。我更改了一点符号并添加了一些功能

#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

3

也可以在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

3

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变量就会被初始化。

这不会与其余评论意见相抵触,实际上,它遵循在全局范围内初始化变量的相同原理,但是通过使用此方法,我们使它变得明确(并且易于理解),而不是具有定义变量挂在那里。


3

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

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

对于以C ++开头的人来说,这是一个普遍的问题。静态类成员必须在单个转换单元(即单个源文件)中初始化。

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

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

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

1
就C ++而言,我仍然是一个完整的n00b,但这对我来说真是太好了,非常感谢!我免费获得完善的单例对象生命周期管理。
拉斐尔·基托佛

2

定义常量的一种“老派”方法是将其替换为enum

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

这种方式不需要提供定义,并且避免了使用常量lvalue,这可以为您省去一些麻烦,例如,当您不小心使用ODR时。


1

当我第一次遇到这个问题时,我只是想提一些对我来说有些奇怪的事情。

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

在.h或.hpp中,看起来像这样来初始化模板类的静态数据成员:

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

0

这符合您的目的吗?

//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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.