C ++でプライベート静的メンバーを初期化する方法は?
-
06-07-2019 - |
質問
プライベートな静的データメンバーを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の場合、C ++
、 char
)。次に、ヘッダーファイルのクラス宣言内でメンバー変数を直接宣言および初期化できます。
class foo
{
private:
static int const i = 42;
};
他のヒント
変数の場合:
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;
};
この質問の今後の視聴者のために、 monkey0506が提案しているものを避ける必要があることを指摘したいと思います。
ヘッダーファイルは宣言用です。
ヘッダーファイルは、直接または間接的に #includes
するすべての .cpp
ファイルごとに1回コンパイルされ、関数の外部のコードはプログラムの初期化時に、 main()
。
foo :: i = VALUE;
をヘッダーに挿入すると、 foo:i
に値 VALUE
が割り当てられますis)すべての .cpp
ファイルに対して、これらの割り当ては main()
が実行される前に不確定な順序(リンカーによって決定)で行われます。
.cpp
ファイルの1つで #define VALUE
を異なる数値にするとどうなりますか?それはうまくコンパイルされ、プログラムを実行するまでどちらが勝つかを知る方法がありません。
.cpp
ファイルを #include
しないことと同じ理由で、実行されたコードをヘッダーに入れないでください。
インクルードガード(常に使用することに同意します)は、別の何かから保護します。同じヘッダーが、単一の .cpp
をコンパイルするときに間接的に複数回 #include
dされるファイル
C ++ 17以降、静的メンバーは inline キーワードを使用してヘッダーに定義できます。
http://en.cppreference.com/w/cpp/language/static
"静的データメンバーはインラインで宣言できます。インライン静的データメンバーは、クラス定義で定義でき、デフォルトのメンバー初期化子を指定できます。クラス外の定義は必要ありません:"
struct X
{
inline static int n = 1;
};
Microsoftコンパイラー[1]では、 int
に似ていない静的変数もヘッダーファイルで定義できますが、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ガード。これは数時間前にParanaixが指摘したように、複数定義エラーを防ぎます。既に別の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()
メソッド内の静的変数であるため、1回だけ構築されます。つまり、コンストラクターが1回呼び出されます。これにより、変数が必要な値に _list
初期化されます。その後の getList
の呼び出しは、すでに初期化された _list
オブジェクトを返すだけです。
もちろん、 getList()
メソッドを呼び出して、常に _list
オブジェクトにアクセスする必要があります。
ヘッダーガードを使用する場合は、ヘッダーファイルに割り当てを含めることもできます。作成したC ++ライブラリにこの手法を使用しました。同じ結果を達成する別の方法は、静的メソッドを使用することです。たとえば...
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
変数が初期化されます。
これはコメントの残りの部分と意見の相違はありません。実際には、グローバルスコープで変数を初期化するのと同じ原則に従いますが、このメソッドを使用することにより、変数の定義がそこにぶら下がっています。
発生したリンカーの問題の原因は、おそらく次のとおりです。
- ヘッダーファイルでクラスと静的メンバーの両方の定義を提供する
- このヘッダーを2つ以上のソースファイルに含めます。
これは、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;
}