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

时间:2008-10-09 03:34:35

标签: c++ initialization static-members

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

17 个答案:

答案 0 :(得分:497)

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

class foo
{
    private:
        static int i;
};

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

int foo::i = 0;

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

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

char

答案 1 :(得分:84)

对于变量

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 :(得分:26)

对于此问题的未来观众,我想指出您应该避免使用monkey0506 is suggesting

头文件用于声明。

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

通过将foo::i = VALUE;添加到标头中,foo:i将为每个VALUE文件分配值.cpp(无论是什么),这些分配将在在main()运行之前的不确定顺序(由链接器确定)。

如果我们在#define VALUE个文件中.cpp成为不同的数字,该怎么办?它会编译得很好,在我们运行程序之前,我们无法知道哪一个获胜。

永远不要将已执行的代码放入标题中,原因与您永远不会#include .cpp个文件相同。

包括警卫(我同意你应该经常使用)保护你免受不同之处:在编译单个#include文件时多次间接.cpp d的同一标题

答案 3 :(得分:21)

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

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

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

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

答案 4 :(得分:19)

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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

答案 5 :(得分:16)

int foo::i = 0; 

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

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

答案 6 :(得分:11)

我没有足够的代表将此添加为评论,但是无论如何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文件。当然,你可以,但没有技术原因你应该这么做。

答案 7 :(得分:10)

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

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

答案 8 :(得分:5)

如果使用标题保护,您还可以在标题文件中包含该分配。我已经将这种技术用于我创建的C ++库。实现相同结果的另一种方法是使用静态方法。例如......

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

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

以上代码具有不需要CPP /源文件的“奖励”。同样,我用于C ++库的方法。

答案 9 :(得分: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

答案 10 :(得分:4)

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

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

GitHub upstream

另请参阅:static constructors in C++? I need to initialize private static objects

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

答案 11 :(得分: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

答案 12 :(得分: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变量就会被初始化。

这不会与其他评论产生分歧,实际上它遵循在全局范围内初始化变量的相同原则,但是通过使用此方法,我们使其明确(并且易于理解)而不是将变量的定义挂在那里。

答案 13 :(得分:2)

您遇到的链接器问题可能是由:

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

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

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

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

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

答案 14 :(得分:1)

当我第一次遇到这个时,我只是想提一些有点奇怪的东西。

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

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

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

答案 15 :(得分:1)

一个&#34;老派&#34;定义常量的方法是用enum

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

这种方式不需要提供定义,并且避免产生常量lvalue,这可以省去一些麻烦,例如:当你不小心ODR-use时。

答案 16 :(得分: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;
}