93

C++11 より前は、整数型または列挙型の static const メンバーに対してのみクラス内初期化を実行できました。Stroustrup は C++ FAQでこれについて説明し、次の例を示しています。

class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

そして、次の推論:

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

ただし、C++11 ではこれらの制限が緩和され、非静的メンバーのクラス内初期化が可能になります (§12.6.2/8)。

非委任コンストラクターで、特定の非静的データ メンバーまたは基底クラスがmem-initializer-idによって指定されていない場合 (コンストラクターにctor-initializerがないためにmem-initializer-listがない場合を含む)エンティティが抽象クラス (10.4) の仮想基底クラスではない場合、

  • エンティティが、 brace-or-equal-initializerを持つ非静的データ メンバーである場合、エンティティは8.5 で指定されているように初期化されます。
  • それ以外の場合、エンティティがバリアント メンバー (9.5) の場合、初期化は実行されません。
  • それ以外の場合、エンティティはデフォルトで初期化されます (8.5)。

セクション 9.4.2 では、指定子でマークされている場合、非 const 静的メンバーのクラス内初期化も許可されていconstexprます。

では、C++03 にあった制限の理由はどうなったのでしょうか? 「複雑なリンカ規則」を受け入れるだけですか、それともこれを実装しやすくするために何か他の変更を加えていますか?

4

3 に答える 3

70

簡単に言えば、以前よりもコンパイラをさらに複雑にすることを犠牲にして、リンカをほぼ同じに保ったということです。

つまり、これによりリンカーが整理する複数の定義が生じるのではなく、1 つの定義しか得られず、コンパイラーはそれを整理する必要があります。

また、プログラマーが整理しておくべきやや複雑なルールにもつながりますが、ほとんどの場合、大したことではないほど単純です。1 つのメンバーに対して 2 つの異なる初期化子が指定されている場合、追加の規則が適用されます。

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

aここでの追加のルールは、デフォルト以外のコンストラクターを使用するときに初期化に使用される値を処理します。それに対する答えは非常に単純です。他の値を指定しないコンストラクタを使用する1234と、初期化に が使用されますaが、他の値を指定するコンストラクタを使用すると、1234は基本的に無視されます。

例えば:

#include <iostream>

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}

    friend std::ostream &operator<<(std::ostream &os, X const &x) { 
        return os << x.a;
    }
};

int main() { 
    X x;
    X y{5678};

    std::cout << x << "\n" << y;
    return 0;
}

結果:

1234
5678
于 2012-12-01T18:51:40.480 に答える
9

テンプレートが完成する前に推論が書かれたのではないかと思います。C++11 がテンプレートの静的メンバーをサポートするには、静的メンバーのクラス内イニシャライザーに必要なすべての「複雑なリンカー規則」が必要でした。

検討

struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
                                                   // thanks @Kapil for pointing that out

// vs.

template <class T>
struct B { static int s; }

template <class T>
int B<T>::s = ::ComputeSomething();

// or

template <class T>
void Foo()
{
    static int s = ::ComputeSomething();
    s++;
    std::cout << s << "\n";
}

コンパイラの問題は、3 つのケースすべてで同じです。つまり、どの翻訳単位で定義を発行し、sそれを初期化するために必要なコードを発行する必要があるのでしょうか。簡単な解決策は、それをどこにでも発行し、リンカーにそれを整理させることです。そのため、リンカーはすでに__declspec(selectany). それなしでは C++03 を実装することはできなかったでしょう。そのため、リンカーを拡張する必要はありませんでした。

もっと率直に言えば、古い基準で与えられた理由付けは明らかに間違っていると思います。


アップデート

Kapil が指摘したように、私の最初の例は現在の標準 (C++14) でも許可されていません。IMOは実装(コンパイラ、リンカー)にとって最も難しいケースであるため、とにかく残しました。私のポイントは、その場合でも、たとえばテンプレートを使用する場合など、既に許可されているものよりも難しくありません。

于 2016-01-29T18:29:34.943 に答える