web-dev-qa-db-ja.com

ヘッダーの静的メンバーを初期化する方法

静的メンバーを持つクラスが与えられます。

class BaseClass
{
public:
    static std::string bstring;
};

文字列は明らかにクラスの外でdefault-initializedでなければなりません。

std::string BaseClass::bstring {"."};

上記の行をクラスとともにヘッダーに含めると、symbol multiply definedエラーが発生します。 include guardsまたはpragma onceであっても、個別のcppファイルで定義する必要があります。

ヘッダーで定義する方法はありませんか?

34
Appleshell

staticメンバー変数を複数回定義することはできません。変数定義をヘッダーに入れると、ヘッダーが含まれる各翻訳単位で定義されます。インクルードガードは1つの翻訳単位のコンパイルにのみ影響するため、役にも立たないでしょう。

ただし、can define static member functions!さて、一見、それはもちろん、その関数がローカルstatic変数を持ち、これらの1つへの参照を返すことが、ほぼstaticメンバーのように振る舞うことを除いて、助けになるようには見えないかもしれません変数:

static std::string& bstring() { static std::string rc{"."}; return rc; }

ローカルのstatic変数は、この関数が最初に呼び出されたときに初期化されます。つまり、関数が最初にアクセスされるまで構築は遅延します。もちろん、この関数を使用して他のグローバルオブジェクトを初期化する場合、オブジェクトが時間内に構築されることも確認できます。複数のスレッドを使用する場合、これは潜在的なデータ競合のように見えますが、そうではありません(C++ 03を使用しない限り)。関数local static変数の初期化はスレッドセーフです。

56
Dietmar Kühl

について

ヘッダーに[静的データメンバー]を定義する方法はありませんか?

はいあります。

template< class Dummy >
struct BaseClass_statics
{
    static std::string bstring;
};

template< class Dummy >
std::string BaseClass_statics<Dummy>::bstring = ".";

class BaseClass
    : public BaseClass_statics<void>
{};

Dietmarが示唆したように、代替手段は関数を使用することです。基本的にそれはマイヤーズのシングルトンです(グーグルit)。

Edit:また、この回答が投稿されてから、インラインオブジェクトの提案があります。これはC++ 17で受け入れられると思います。

とにかく、二度考えるここでの設計について。グローバル変数はEvil™です。これは本質的にグローバルです。

10

C++ 17では、インライン変数を使用できます。これはoutsideクラスでも使用できます。

インライン指定子は、静的ストレージ期間(静的クラスメンバーまたは名前空間スコープ変数)を持つ変数のdecl-specifier-seqで使用される場合、変数がインライン変数であることを宣言します。

Constexprで宣言された静的メンバー変数(名前空間スコープ変数ではない)は、暗黙的にインライン変数です。⁽¹⁾

例えば:

class Someclass {
public:
    inline static int someVar = 1;
};

または、

namespace SomeNamespace {
    inline static int someVar = 1;
}

⁽¹⁾ https://en.cppreference.com/w/cpp/language/inline

5

C++ 11の宣言で静的値の定義を保持するには、ネストされた静的構造を使用できます。この場合、静的メンバーは構造体であり、.cppファイルで定義する必要がありますが、値はヘッダーにあります。

class BaseClass
{
public:
  static struct _Static {
     std::string bstring {"."};
  } global;
};

個々のメンバーを初期化する代わりに、静的構造全体が初期化されます。

BaseClass::_Static BaseClass::global;

値にアクセスするには

BaseClass::global.bstring;

この解決策には、静的変数の初期化の順序の問題があることに注意してください。静的値を使用して別の静的変数を初期化する場合、最初の変数はまだ初期化されていない可能性があります。

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

この場合、静的変数headersには、リンカによって作成された初期化の順序に応じて、{""}または{".h"、 ".hpp"}のいずれかが含まれます。

4
Marko Mahnič

いいえ、ヘッダーで行うことはできません-少なくともソースファイルにヘッダーが複数回含まれている場合はそうであり、そうでなければエラーは発生しません。 .cppファイルの1つに貼り付けて完了です。

3
Mats Petersson

§3.2.6および現在のc ++ 17ドラフト(n4296)の次の段落は、異なる翻訳単位に複数の定義が存在する可能性がある場合のルールを定義しています。

クラス型(9節)、列挙型(7.2)、外部リンケージ付きインライン関数(7.1.2)、クラステンプレート(14節)、非静的関数テンプレート(14.5.6)の定義が複数存在する場合があります。 、クラステンプレートの静的データメンバー(14.5.1.3)、クラステンプレートのメンバー関数(14.5.1.1)、またはいくつかのテンプレートパラメーターが指定されていないテンプレート特殊化(14.7、14.5.5)定義は別の翻訳単位に表示されますが、定義が次の要件を満たしている必要があります。このようなDという名前のエンティティが複数の翻訳単位で定義されている場合、[...]

明らかに、クラス型の静的データメンバーの定義は、複数の翻訳単位に現れるとは見なされません。したがって、標準によれば、許可されていません

Cheers and hthからの提案された回答。 -アルフとディートマーは、「ハック」の一種であり、その定義を活用しています

クラステンプレートの静的データメンバー(14.5.1.3)

そして

外部リンケージ付きインライン関数(7.1.2)

複数のTU(FYI:クラス定義内で定義された静的関数は外部リンケージを持ち、暗黙的にインラインとして定義されます)で許可されます)。

3
SebNag

更新:以下の私の答えは、質問で提案された方法でこれを行うことができない理由を説明しています。これを回避する少なくとも2つの答えがあります。問題を解決する場合もしない場合もあります。


bstring静的メンバーは、特定のメモリアドレスにリンクする必要があります。これを行うには、単一のオブジェクトファイルに表示する必要があるため、単一のcppファイルに表示する必要があります。これを確実にするために#ifdefで遊んでいない限り、ヘッダーファイルは複数のcppファイルに含まれている可能性があるため、ヘッダーファイルで必要なことはできません。

1
nickie