クラスの非定数静的メンバーまたは静的配列を初期化できないのはなぜですか?


116

クラスの非const 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ですか?

1
主な理由は、正しく理解するのが難しいことだと思います。原則として、あなたはおそらくあなたが話していることをすることができますが、いくつかの奇妙な副作用があります。配列の例が許可されている場合と同様に、A :: c [0]の値を取得することはできますが、アドレスを必要とするため、A :: cを関数に渡すことができず、コンパイル時に定数にはアドレスがありません。C ++ 11では、constexprを使用することにより、この一部を有効にしています。
Vaughn Cato、2012年

すばらしい質問と不正解。私を助けたリンク:msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

回答:


144

staticクラスのデータメンバーを初期化できないのはなぜですか?

C ++標準では、静的定数の整数型または列挙型のみをクラス内で初期化できます。これが、a初期化が許可されているのに他のユーザーが初期化できない理由です。

参照:
C ++ 03 9.4.2静的データメンバー
§4

静的データメンバーがconst整数型またはconst列挙型である場合、クラス定義での宣言は、定数定数式(5.19)である定数初期化子を指定できます。その場合、メンバーは整数定数式で表すことができます。メンバーは、プログラムで使用され、名前空間スコープ定義に初期化子が含まれていない場合、名前空間スコープで定義されます。

整数型とは何ですか?

C ++ 03 3.9.1基本型
§7

タイプbool、char、wchar_t、および符号付きと符号なし整数型は、まとめて整数型と呼ばれます。43)整数型の同義語は整数型です。

脚注:

43)したがって、列挙(7.2)は不可欠ではありません。ただし、4.5で指定されているように、列挙型はint、unsigned int、long、またはunsigned longに昇格できます。

回避策:

列挙型トリックを使用して、クラス定義内の配列を初期化できます。

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

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

};

なぜ規格はこれを許可しないのですか?

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

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

static constクラス内初期化が許可されるのはなぜ整数型と列挙型だけなのですか?

答えはBjarneの引用に隠され
ています。「C ++ではすべてのオブジェクトに一意の定義が必要です。C++がオブジェクトとしてメモリに格納する必要のあるエンティティのクラス内定義を許可した場合、そのルールは破られます。」

static constコンパイル時定数として扱うことができるのは整数のみであることに注意してください。コンパイラーは整数値がいつでも変更されないことを知っているため、独自のマジックを適用して最適化を適用できます。コンパイラーは、そのようなクラスメンバーを単にインライン化します。つまり、それらはもはやメモリに格納されません。 、それはBjarneによって言及されたルールの例外をそのような変数に与えます。

ここで、static const整数値がクラス内初期化を持つことができる場合でも、そのような変数のアドレスを取得することは許可されていないことに注意してください。静的メンバーのアドレスを取得するには、クラス外の定義がある場合に限ります。これにより、上記の理由がさらに検証されます。

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


これはC ++ 11でどのように変わりますか?

C ++ 11は制限をある程度緩和します。

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

静的データメンバーがconstリテラル型である場合、クラス定義での宣言は、brace-or-equal-initializerを指定できます。この場合assignment-expressionであるすべてのinitializer-clauseは定数式です。リテラルタイプの静的データメンバが持つクラス定義内で宣言することができそうである場合、その宣言は、指定しなければならないブレース-OR-等しい初期におけるすべての初期化句である代入式をconstexpr specifier;定数式です。[注:これらのどちらの場合でも、メンバーは定数式に現れることがあります。—end note]メンバーがプログラムで使用され、名前空間スコープの定義に初期化子が含まれていない場合、メンバーは名前空間スコープで定義されます。

また、C ++ 11はだろう、それが宣言される非静的データメンバは、(そのクラスに)初期化する(§12.6.2.8)を可能にします。これは、非常に簡単なユーザーセマンティクスを意味します。

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


7
c ++ 11では状況が異なります。回答は更新を使用できます。
bames53 2012

4
これは真実ではないようです:「静的な定数の整数だけがコンパイル時定数として扱われることに注意してください。コンパイラは整数値がいつでも変更されないことを知っているため、独自の魔法を適用して最適化を適用できます。コンパイラは単にインラインようなクラスのメンバーは、すなわち、それらはもはやメモリに格納されていない「、 あなたは必ず彼らがしているです必ずしもメモリに格納されていませんか?メンバーの定義を提供するとどうなりますか?何が&member返りますか?
Nawaz、2012年

2
@アルス:うん。それが私の質問です。では、なぜC ++が整数型のみのクラス内初期化を許可するのか、あなたの答えでは正しく答えられないのはなぜですか?static const char*メンバーの初期化を許可しない理由を考えてみてください。
Nawaz、2012年

3
@Nawaz:C ++ 03では、静的初期化子とconst整数型およびconst列挙型のみが許可され、他の型は許可されないため、C ++ 11はこれを、クラス内初期化の基準を緩和するconstリテラル型に拡張します。C ++ 03はおそらく変更を保証する見落としであったため、C ++ 11で修正されましたが、変更の伝統的な戦術的な理由がある場合、私はそれらを認識していません。それら。
Alok Save

4
あなたが言及した「回避策」のパー g ++では動作ません
iammilind 2012年

4

これは、昔の単純なリンカの遺物と思われます。回避策として、静的メソッドで静的変数を使用できます。

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

そして

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

そして

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

ビルド:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

実行:

./main

これが機能するという事実(一貫して、クラス定義が異なるコンパイル単位に含まれている場合でも)は、今日のリンカー(gcc 4.9.2)が実際に十分にスマートであることを示しています。

面白い:0123腕と3210x86に印刷します。


1

宣言と定義を混同しないようにするためだと思います。(ファイルを複数の場所に含める場合に発生する可能性がある問題について考えてください。)


0

これはA::a、すべての翻訳単位が使用する定義が1つしかないためです。

static int a = 3;すべての翻訳単位に含まれるヘッダーのクラスで実行した場合、複数の定義が得られます。そのため、staticの行外でない定義は、強制的にコンパイラエラーになります。

これを使用static inlineまたはstatic const修正します。static inlineシンボルが翻訳単位で使用されている場合にのみ具体化し、comdatグループ内にあるために複数の翻訳単位で定義されている場合、リンカーは1つのコピーのみを選択して残すようにします。constatファイルスコープexternでは、クラスで許可されていないが使用されていない限り、常にコード内で置き換えられるため、コンパイラはシンボルを発行しません。

もう一つ注意すべきは、されstatic inline int b;、一方、定義として扱われているstatic const int bか、static const A b;まだ宣言として扱われ、あなたがクラス内に定義されていない場合はアウトオブライン定義する必要があります。興味深いことにstatic constexpr A b;、定義として扱われstatic constexpr int b;ますが、エラーであり、初期化子が必要です(これは、定義になり、ファイルスコープでのconst / constexpr定義と同様に、intにはないがクラス型の初期化子が必要になるためです)= A()定義の場合は暗黙的であるためです-clangはこれを許可しますが、gccは明示的に初期化する必要があるか、エラーです。これはインラインの問題ではありません)。static const A b = A();許可されておらず、constexprまたはinlineクラス型の静的オブジェクトのイニシャライザを許可するため。つまり、クラス型の静的メンバーを宣言より多くするため。そうです、特定の状況では、A a;明示的に初期化するのと同じではありませんA a = A();前者は宣言することができますが唯一の宣言は、その型に許されるならば、後者はエラーです(。唯一の定義に使用される。後者の缶はconstexprそれ定義可能)。constexprデフォルトのコンストラクタを使用して指定する場合、コンストラクタは次のようにする必要がありますconstexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

静的メンバーは完全なファイルスコープ宣言ですextern int A::a;(クラスでのみ作成でき、行外定義はクラスの静的メンバーを参照する必要があり、定義でなければならず、externを含めることはできません)。一方、非静的メンバーはクラスの完全な型定義であり、なしのファイルスコープ宣言と同じルールがありますextern。これらは暗黙的に定義されています。int i[]; int i[5];再定義もそうですが、そうではstatic int i[]; int A::i[5];ありませんが、2つの外部定義とは異なりstatic int i[]; static int i[5];、クラスで行うとコンパイラは重複メンバーを検出します。


-3

静的変数はクラスに固有です。コンストラクターは、インスタンスの属性ESPECIALYを初期化します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.