web-dev-qa-db-ja.com

C ++ 11では、非静的および非constメンバーのクラス内初期化が可能です。何が変わった?

C++ 11より前は、整数型または列挙型の静的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で指定されていない場合(mem-initializer-listコンストラクターにはctor-initializer)がなく、エンティティは抽象クラスの仮想基本クラスではないため( 10.4)、その後

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

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

それでは、C++ 03にあった制限の理由はどうなりましたか? 「複雑なリンカルール」を単に受け入れるか、これを実装しやすくする他の何かが変更されていますか?

79

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

つまり、これによりリンカーが複数の定義をソートする代わりに、1つの定義のみが生成され、コンパイラーはそれをソートする必要があります。

また、programmerの規則をやや複雑にすることもできますが、ほとんど単純なので大したことではありません。単一のメンバーに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
62
Jerry Coffin

テンプレートが完成する前に推論が書かれたのではないかと思います。静的メンバーのクラス内初期化子に必要なすべての「複雑なリンカールール」は、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を実装することは不可能でした。そして、それがリンカを拡張する必要がなかった理由です。

もっと率直に言うと、古い標準で与えられた推論は、まったく間違っていると思います。


UPDATE

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

8
Paul Groke

理論的にはSo why do these inconvenient restrictions exist?...理由は有効ですが、かなり簡単に迂回することができ、これはまさにC++ 11が行うことです。

ファイルをincludeすると、ファイルが単に含まれ、初期化は無視されます。メンバーが初期化されるのは、クラスをinstantiateした場合のみです。

つまり、初期化は依然としてコンストラクターに関連付けられており、表記法が異なるだけで便利です。コンストラクターが呼び出されない場合、値は初期化されません。

コンストラクターが呼び出された場合、値はクラス内の初期化(存在する場合)で初期化されます。コンストラクターはそれを独自の初期化でオーバーライドできます。初期化のパスは基本的に同じです。つまり、コンストラクターを使用します。

これは、C++ 11のStroustrup独自の FAQ から明らかです。

6
zar