1. ホーム
  2. c++

[解決済み] コンストラクタでない静的メンバや静的配列をクラスで初期化できないのはなぜですか?

2022-06-19 05:29:07

質問

コンストラクタでない static メンバまたは static の配列は?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

を実行すると、コンパイラは次のようなエラーを出します。

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member ‘b’
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type ‘const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type ‘int [2]’

2つ質問があります。

  1. なぜ私は初期化できないのですか? static のデータメンバを初期化できないのですか?
  2. を初期化できないのはなぜですか? static の配列も初期化できないのでしょうか? const の配列は?

どのように解決するには?

初期化できない理由 static のデータメンバを初期化できないのですか?

C++の標準では、クラス内部で初期化できるのは静的定数積分型または列挙型のみです。このため a が初期化され、他が初期化されないのはこのためです。

参考にしてください。

C++03 9.4.2 静的データメンバ

§4

静的データメンバがconst integral型またはconst enumeration型の場合、クラス定義におけるその宣言は、積分定数式でなければならない定数初期化子を指定することができます(5.19)。この場合,そのメンバは,積分定数式に現れることができる。そのメンバがプログラムで使用され,名前空間スコープ定義に初期化子が含まれてはならない場合,そのメンバは,依然として名前空間スコープで定義されなければならない。

積分型とは何ですか?

C++03 3.9.1 基本的な型

§7

bool型、char型、wchar_t型、符号付き整数型、符号なし整数型を総称して整数型と呼びます43)。整数型の同義語は整数型です。

脚注です。

<ブロッククオート

43) したがって、列挙型(7.2)は積分型ではありません。しかし、列挙型は4.5で規定されているように、int、unsigned int、long、unsigned longに昇格させることが可能です。

回避策です。

この場合 enum のトリック を使って、クラス定義の中で配列を初期化することができます。

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

なぜStandardはこれを許さないのですか?

Bjarneはこれを適切に説明しています。 ここで :

クラスは通常ヘッダーファイルで宣言され、ヘッダーファイルは通常多くの翻訳ユニットに含まれます。しかし、複雑なリンカー ルールを避けるため、C++ はすべてのオブジェクトが一意の定義を持つことを要求しています。C++ がオブジェクトとしてメモリに格納する必要のあるエンティティのクラス内定義を許可した場合、そのルールは破られることになります。

なぜ static const integral types & enums だけがクラス初期化を許されるのですか?

答えはBjarneの引用文に隠されています......よく読んでください。

C++ は、すべてのオブジェクトが一意の定義を持つことを要求しています。C++ がオブジェクトとしてメモリに格納する必要のあるエンティティのクラス内定義を許可した場合、そのルールは破られるでしょう。

のみであることに注意してください。 static const のみがコンパイル時定数として扱われることに注意してください。コンパイラは整数の値がいつでも変化するわけではないことを知っているので、独自のマジックを適用して最適化を行うことができ、コンパイラは単にそのようなクラスメンバーをインライン化します。

ここで注目すべきなのは、たとえ static const がクラス内初期化を行えるとしても、そのような変数のアドレスの取得は許可されません。静的メンバがクラス外の定義を持っている場合(およびその場合のみ)、そのアドレスを取得することができます。

列挙型の値は int が期待される場所で使用できるため、列挙型は許可されています。 上記の引用を参照してください。


C++11 ではどのように変更されるのですか。

C++11 では、制限がある程度緩和されます。

C++11 9.4.2 静的データメンバ

§3

静的データメンバが const リテラル型である場合、クラス定義におけるその宣言では、静的データメンバを指定するために brace-or-equal-initializer を指定することができ、その中ですべての 初期化子節 であり 代入式 は定数式です。リテラル型の静的データメンバは、クラス定義で constexpr specifier; を指定することができ、その場合、その宣言は brace-or-equal-initializer を指定し,その中で,すべての 初期化子節 であり 代入式 は定数式です。[ 注意: これらの両方の場合において、メンバーは定数式に現れることがあります。-注を終了する ]。メンバがプログラムで使用される場合も、名前空間スコープで定義しなければならず、名前空間スコープの定義に初期化子を含んではならない。

また、C++11 では は、非静的データメンバを宣言された場所(そのクラス内)で初期化することを許可します(§12.6.2.8)。これは、ユーザーのセマンティクスをより簡単にすることを意味します。

これらの機能は最新のgcc 4.7ではまだ実装されていないため、コンパイルエラーが発生する可能性があることに注意してください。