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


520

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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


2
こんにちはジェイソン。静的メンバー(特に整数のメンバー)のデフォルトの初期化に関するコメントは見つかりませんでした。実際、リンカがそれを見つけることができるように、int foo :: iを記述する必要がありますが、0で自動的に初期化されます。この行で十分です:int foo :: i; (これは、静的メモリに格納されているすべてのオブジェクトに対して有効です。リンカーは静的オブジェクトの初期化を担当します。)
Nico

1
以下の回答はテンプレートクラスには適用されません。彼らは言う:初期化はソースファイルに行く必要があります。テンプレートクラスの場合、これは不可能であり、必要でもありません。
Joachim W

7
C ++ 17では、静的データメンバーのインライン初期化が可能です(非整数型の場合でも)inline static int x[] = {1, 2, 3};en.cppreference.com/w/cpp/language/static#Static_data_membersを
Vladimir Reshetnikov

回答:


557

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

class foo
{
    private:
        static int i;
};

ただし、初期化はソースファイルにある必要があります。
ファイル:foo.cpp

int foo::i = 0;

初期化がヘッダーファイル内にある場合、ヘッダーファイルを含む各ファイルには静的メンバーの定義があります。したがって、リンクフェーズでは、変数を初期化するコードが複数のソースファイルで定義されるため、リンカーエラーが発生します。の初期化は、static int i関数の外で行う必要があります。

注:マットカーティス:静的メンバ変数は、CONST int型である場合、C ++は、上記の単純化を可能にすることポイントアウト(例えばintboolchar)。次に、ヘッダーファイルのクラス宣言内で直接メンバー変数を宣言して初期化できます。

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

4
はい。しかし、私は質問が単純化されていると思います。技術的には、宣言と定義をすべて1つのソースファイルに含めることができます。しかし、それによって他のクラスによるクラスの使用が制限されます。
マーティンヨーク

11
実際にはPODだけでなく、int型(int、short、bool、char ...)でもある必要があります
Matt Curtis

9
これは、値が初期化される方法の問題だけではないことに注意してください。このように定義されたconst整数型は、実装によってコンパイル時定数に変換される場合があります。バイナリの依存関係が増えるため、これが常に望ましいとは限りません。値が変更された場合、クライアントコードは再コンパイルする必要があります。
スティーブジェソップ

5
@Martin:修正s / POD /整数型/に加えて、アドレスが取得される場合は、定義も必要です。奇妙に思われるかもしれませんが、初期化子を使用した宣言は、クラス定義では定義ではありません。テンプレートのconstイディオムを使用すると、ヘッダファイル内の定義を必要とする場合の回避策を提供します。もう1つのより簡単な回避策は、ローカル静的定数の値を生成する関数です。乾杯&hth。、
乾杯とhth。-Alf

3
int foo :: i = 0;という説明を追加できます。関数内(メイン関数を含む)であってはなりません。メイン関数の最初に持っていましたが、それは好きではありません。
qwerty9967

89

以下のための変数

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

これはfoo::i、プログラムにのインスタンスは1つしか存在できないためです。これ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 ++標準の引用は必要ありません
smRaj

1
privateここでクラスの外で変数を初期化できるのはなぜですか?
クリシュナオザ2015

説明を見つけましたか?@Krishna_Oza
nn0p

@ nn0pはまだですが、静的でClassはないプライベート変数の初期化は、Cppでは意味がありません。
クリシュナオザ2016

41

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

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

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

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

1
これは、現在、新しい標準になりつつあるC ++ 17以降で可能です。
グレブ2017

31

この質問の今後の視聴者のために、私はあなたがmonkey0506が示唆していることを避けるべきであることを指摘したいと思います。

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

ヘッダーファイルは.cpp、直接または間接的に#includesそれらのファイルごとに1回コンパイルされ、関数の外部のコードは、プログラムの初期化時に実行されます。main()

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

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

あなたは決してその同じ理由ヘッダーに実行されるコードを置いてはいけませんファイル。#include.cpp

ガードを含める(私はあなたが常に使うべきだと私は同意します)を何か別のものから保護します:#include単一の.cppファイルをコンパイルしている間、同じヘッダーが間接的に複数回d されます


2
もちろん、これについては正しいですが、クラステンプレートの場合は例外です(質問はされませんが、たまたま私は多くのことを扱っています)。したがって、クラスが完全に定義され、クラステンプレートではない場合、これらの静的メンバーを別のCPPファイルに入れますが、クラステンプレートの場合、定義は同じ変換単位(ヘッダーファイルなど)になければなりません。
monkey0506 2013年

@ monkey_05_06:これは、テンプレートコードで静的メンバーを回避するための引数のようです:クラスのインスタンス化ごとに1つの静的メンバーが既に存在します。ヘッダーを複数のcppファイルにコンパイルする可能性があるため、問題はさらに悪化します...競合する多数の定義を取得する可能性があります。
ジョシュアクレイトン

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/…このリンクは、静的なテンプレートメンバーをmain関数にインスタンス化することを表しています。少し負担がある場合は、よりクリーンです。
ジョシュアクレイトン

1
あなたの議論は本当に巨大です。まず、マクロ名が有効な識別子ではないため、VALUEを#defineできません。そして、あなたができたとしても、誰がそうするでしょうか?ヘッダーファイルは宣言用です-?ほら、ヘッダに値を入れないようにするべき唯一のケースは、odr-usedと戦うことです。また、値をヘッダーに入れると、値を変更する必要がある場合はいつでも不要な再コンパイルが発生する可能性があります。
Aleksander Fular 2016年

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)に配置する必要があります。

これは静的変数であるため、コンパイラーはそのコピーを1つだけ作成する必要があります。コードのどこかに「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、2012

3
良いスタイルに関して:あなたは終了endifにコメントを追加する必要があります:#endif // FOO_H
Riga

9
これは、foo.hを含むコンパイルユニットが1つしかない場合にのみ機能します。2つ以上のcppにfoo.hが含まれている場合(これは一般的な状況です)、各cppは同じ静的変数を宣言するため、ファイルでパッケージコンパイル(compileすべてのcppsを含む1つのファイルのみ)。しかし、パッケージのコンパイルは素晴らしいですが、問題の解決策はcintで(int foo :: i = 0;)を宣言することです!
Alejadro Xalabarder 2013

1
または単に使用する#pragma once
タンブル

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()であるため、1回だけ作成されます。つまり、コンストラクターが1回呼び出されます。これは、initialize _list必要な値に変化します。以降の呼び出しでgetListは、単に初期化済みの_listオブジェクトが返されます。

もちろん、_list常にgetList()メソッドを呼び出すことによってオブジェクトにアクセスする必要があります。


1
:ここではメンバオブジェクトごとにメソッドを作成する必要はありません。このイディオムのバージョンであるstackoverflow.com/a/48337288/895245
チロSantilli郝海东冠状病六四事件法轮功

9

複数のオブジェクトで機能するC ++ 11静的コンストラクタパターン

1つのイディオムが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

発生したリンカーの問題は、おそらく次の原因が原因です。

  • ヘッダーファイルでクラスと静的メンバーの両方の定義を提供します。
  • このヘッダーを2つ以上のソースファイルに含める。

これは、C ++で始める人にとって一般的な問題です。静的クラスメンバーは、単一の翻訳単位、つまり単一のソースファイルで初期化する必要があります。

残念ながら、静的クラスメンバーはクラス本体の外で初期化する必要があります。これにより、ヘッダーのみのコードの記述が複雑になるため、まったく異なる方法を使用しています。たとえば、静的または非静的クラス関数を使用して静的オブジェクトを提供できます。

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

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

1
私はまだC ++に関しては完全なn00bですが、これは私には素晴らしいようです、どうもありがとう!シングルトンオブジェクトの完全なライフサイクル管理を無料で受けられます。
Rafael Kitover、

2

定数を定義する「古い」方法の1つは、定数をで置き換えること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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.