C++でプライベートな静的データメンバを初期化するための最良の方法は何ですか?私はこれを私のヘッダファイルで試しましたが、それは私に奇妙なリンカエラーを与えます:
class foo
{
private:
static int i;
};
int foo::i = 0;
これは、クラス外からプライベートメンバーを初期化できないためだと思います。それではこれを行うための最良の方法は何ですか?
クラス宣言は、ヘッダーファイル内(または共有されていない場合はソースファイル内)にあります。
ファイル:foo.h
class foo
{
private:
static int i;
};
しかし、初期化はソースファイルにあるべきです。
ファイル:foo.cpp
int foo::i = 0;
初期化がヘッダーファイル内にある場合、ヘッダーファイルを含む各ファイルは静的メンバーの定義を持ちます。したがって、リンク段階では、変数を初期化するコードが複数のソースファイルで定義されるため、リンカエラーが発生します。
注意: Matt Curtis氏:静的メンバー変数がconst int型(例えばint
、bool
、char
)の場合、C++は上記の単純化を可能にすると指摘しています。その後、ヘッダーファイルのクラス宣言内で直接メンバー変数を宣言して初期化できます。
class foo
{
private:
static int const i = 42;
};
変数 の場合:
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;
};
この質問を将来見る人のために、 monkey0506が を示唆しているのは避けるべきだと私は指摘したい。
ヘッダーファイルは宣言用です。
ヘッダファイルは、直接的または間接的にそれらを.cpp
するすべての#includes
ファイルに対して1回コンパイルされ、関数の外側のコードはmain()
より前のプログラム初期化時に実行されます。
:foo::i = VALUE;
をヘッダーに入れることで、foo:i
にはすべての.cpp
ファイルに対してVALUE
という値が割り当てられ、これらの割り当てはmain()
が実行される前に不確定な順序で行われます。
#define VALUE
ファイルの1つで.cpp
を別の数字にするとどうなりますか?それはうまくコンパイルされ、プログラムを実行するまでどちらが勝つかを知る方法がありません。
#include
ファイルを.cpp
ファイルにしないのと同じ理由で、実行されたコードをヘッダーに入れないでください。
1つの#include
ファイルをコンパイルしている間に、同じヘッダーが間接的に複数回.cpp
dされることから、ガードを含める(私はあなたがいつも使うべきだと思う)
C++ 17以降、静的メンバーは inline キーワードを使用してヘッダーに定義できます。
http://en.cppreference.com/w/cpp/language/static
"静的データメンバはインラインで宣言することができます。インライン静的データメンバはクラス定義内で定義することができ、デフォルトのメンバ初期化子を指定することができます。クラス外定義は必要ありません。"
struct X
{
inline static int n = 1;
};
Microsoftのコンパイラ[1]を使えば、int
-likeではない静的変数もヘッダファイル内で定義することができますが、クラス宣言の外側では、Microsoft固有の__declspec(selectany)
を使用します。
class A
{
static B b;
}
__declspec(selectany) A::b;
私はこれが良いと言っているのではないことに注意してください、私はそれができると言います。
[1]最近、MSCよりも多くのコンパイラが__declspec(selectany)
をサポート - 少なくともgccとclang。さらにもっと。
int foo::i = 0;
変数を初期化するための正しい構文ですが、ヘッダーではなくソースファイル(.cpp)に含める必要があります。
これは静的変数なので、コンパイラはそのコピーを1つだけ作成する必要があります。コードのどこかに "int foo:i"と書く必要があります。そうしないとリンクエラーが発生します。それがヘッダにある場合は、そのヘッダを含むすべてのファイルにコピーがあるので、リンカから多重定義シンボルエラーを受け取ります。
これをコメントとして追加するのに十分な担当者はここにはいませんが、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ファイルを使用する必要はありません。もちろん、できますが、技術的な理由はありません。
もしあなたが何らかの複合型(例えば、文字列)を初期化したいのなら、あなたはそのようなことをすることができます:
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
オブジェクトを単に返すだけです。
もちろん、getList()
メソッドを呼び出すことによって常に_list
オブジェクトにアクセスする必要があります。
ヘッダガードを使用している場合は、割り当てをヘッダファイルに含めることもできます。私が作成したC++ライブラリにこの手法を使用しました。同じ結果を得るためのもう1つの方法は、静的メソッドを使用することです。例えば...
class Foo
{
public:
int GetMyStatic() const
{
return *MyStatic();
}
private:
static int* MyStatic()
{
static int mStatic = 0;
return &mStatic;
}
}
上記のコードにはCPP /ソースファイルを必要としないという「ボーナス」があります。繰り返しますが、私のC++ライブラリに使用する方法です。
私はカールの考えに従います。私はそれが好きです、そして今私もそれを使います。表記を少し変更して機能を追加しました
#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
複数のオブジェクトに対して機能する静的コンストラクタパターン
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);
}
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
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
変数は初期化されます。
これは、他のコメントと矛盾することはありません。実際には、グローバルスコープで変数を初期化するのと同じ原則に従いますが、このメソッドを使用することで、定義を持たずに明示的にしますそこにぶら下がっている変数の。
あなたが遭遇したリンカの問題はおそらく以下によって引き起こされます:
これは、C++を始めた人にとっては一般的な問題です。静的クラスメンバは、単一の翻訳単位、つまり単一のソースファイルで初期化する必要があります。
残念ながら、静的クラスメンバはクラス本体の外部で初期化する必要があります。これはヘッダーのみのコードを書くのを複雑にします、そして、それ故に、私は全く異なるアプローチを使用しています。静的または非静的クラス関数を介して静的オブジェクトを提供できます。
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
私が初めてこれに遭遇したとき、私はちょっと変わったことを私に言及したかっただけです。
テンプレートクラスのプライベート静的データメンバを初期化する必要がありました。
.hまたは.hppでは、テンプレートクラスの静的データメンバを初期化するために次のようになります。
template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
これはあなたの目的にかなっていますか?
//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;
}