web-dev-qa-db-ja.com

C + +でプライベート静的メンバーを初期化する方法?

C++でプライベートな静的データメンバを初期化するための最良の方法は何ですか?私はこれを私のヘッダファイルで試しましたが、それは私に奇妙なリンカエラーを与えます:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

これは、クラス外からプライベートメンバーを初期化できないためだと思います。それではこれを行うための最良の方法は何ですか?

458
Jason Baker

クラス宣言は、ヘッダーファイル内(または共有されていない場合はソースファイル内)にあります。
ファイル:foo.h

class foo
{
    private:
        static int i;
};

しかし、初期化はソースファイルにあるべきです。
ファイル:foo.cpp

int foo::i = 0;

初期化がヘッダーファイル内にある場合、ヘッダーファイルを含む各ファイルは静的メンバーの定義を持ちます。したがって、リンク段階では、変数を初期化するコードが複数のソースファイルで定義されるため、リンカエラーが発生します。

注意: Matt Curtis氏:静的メンバー変数がconst int型(例えばintboolchar)の場合、C++は上記の単純化を可能にすると指摘しています。その後、ヘッダーファイルのクラス宣言内で直接メンバー変数を宣言して初期化できます。

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

変数 の場合:

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;
};
84
Matt Curtis

この質問を将来見る人のために、 monkey0506が を示唆しているのは避けるべきだと私は指摘したい。

ヘッダーファイルは宣言用です。

ヘッダファイルは、直接的または間接的にそれらを.cppするすべての#includesファイルに対して1回コンパイルされ、関数の外側のコードはmain()より前のプログラム初期化時に実行されます。

foo::i = VALUE;をヘッダーに入れることで、foo:iにはすべての.cppファイルに対してVALUEという値が割り当てられ、これらの割り当てはmain()が実行される前に不確定な順序で行われます。

#define VALUEファイルの1つで.cppを別の数字にするとどうなりますか?それはうまくコンパイルされ、プログラムを実行するまでどちらが勝つかを知る方法がありません。

#includeファイルを.cppファイルにしないのと同じ理由で、実行されたコードをヘッダーに入れないでください。

1つの#includeファイルをコンパイルしている間に、同じヘッダーが間接的に複数回.cppdされることから、ガードを含める(私はあなたがいつも使うべきだと思う)

25
Joshua Clayton

C++ 17以降、静的メンバーは inline キーワードを使用してヘッダーに定義できます。

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

"静的データメンバはインラインで宣言することができます。インライン静的データメンバはクラス定義内で定義することができ、デフォルトのメンバ初期化子を指定することができます。クラス外定義は必要ありません。"

struct X
{
    inline static int n = 1;
};
20
Die in Sente

Microsoftのコンパイラ[1]を使えば、int-likeではない静的変数もヘッダファイル内で定義することができますが、クラス宣言の外側では、Microsoft固有の__declspec(selectany)を使用します。

class A
{
    static B b;
}

__declspec(selectany) A::b;

私はこれが良いと言っているのではないことに注意してください、私はそれができると言います。

[1]最近、MSCよりも多くのコンパイラが__declspec(selectany)をサポート - 少なくともgccとclang。さらにもっと。

19
Johann Gerell
int foo::i = 0; 

変数を初期化するための正しい構文ですが、ヘッダーではなくソースファイル(.cpp)に含める必要があります。

これは静的変数なので、コンパイラはそのコピーを1つだけ作成する必要があります。コードのどこかに "int foo:i"と書く必要があります。そうしないとリンクエラーが発生します。それがヘッダにある場合は、そのヘッダを含むすべてのファイルにコピーがあるので、リンカから多重定義シンボルエラーを受け取ります。

16
David Dibben

これをコメントとして追加するのに十分な担当者はここにはいませんが、IMOは #include guards と一緒にあなたのヘッダを書くのに良いスタイルです。 。すでに別のCPPファイルを使用していない限り、静的な非整数メンバを初期化するためだけに使用する必要はありません。

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

これには別のCPPファイルを使用する必要はありません。もちろん、できますが、技術的な理由はありません。

11
monkey0506

もしあなたが何らかの複合型(例えば、文字列)を初期化したいのなら、あなたはそのようなことをすることができます:

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オブジェクトにアクセスする必要があります。

10

ヘッダガードを使用している場合は、割り当てをヘッダファイルに含めることもできます。私が作成したC++ライブラリにこの手法を使用しました。同じ結果を得るためのもう1つの方法は、静的メソッドを使用することです。例えば...

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

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

上記のコードにはCPP /ソースファイルを必要としないという「ボーナス」があります。繰り返しますが、私のC++ライブラリに使用する方法です。

5
user2225284

私はカールの考えに従います。私はそれが好きです、そして今私もそれを使います。表記を少し変更して機能を追加しました

#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
4

複数のオブジェクトに対して機能する静的コンストラクタパターン

1つのイディオムが提案されました: 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アップストリーム

C++の静的コンストラクタ?プライベート静的オブジェクトを初期化する必要があります も参照してください。

g++ -std=c++11 -Wall -Wextra、GCC 7.3、Ubuntu 18.04でテスト済み。

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
andrew

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;
    }
};
2
no one special

定数を定義するための "昔ながらの"方法の1つは、それらをenumに置き換えることです。

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

この方法は定義を提供することを必要とせず、定数 左辺値 を作ることを避けます。あなたが誤って ODR-use itを使っているとき。

1
anatolyg

私が初めてこれに遭遇したとき、私はちょっと変わったことを私に言及したかっただけです。

テンプレートクラスのプライベート静的データメンバを初期化する必要がありました。

.hまたは.hppでは、テンプレートクラスの静的データメンバを初期化するために次のようになります。

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

これはあなたの目的にかなっていますか?

//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;
}
0
David Nogueira