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


87

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で指定されていない場合(コンストラクターにctor-initializerがないためにmem-initializer-listがない場合を含む)エンティティが抽象クラス(10.4)の仮想基本クラスではない場合、

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

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

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


5
何も起こらなかった。コンパイラは、これらすべてのヘッダーのみのテンプレートでよりスマートに成長したため、現在は比較的簡単に拡張できます。
OO Tiib

興味深いことに、C ++ 11より前のコンパイルを選択すると、IDEで非静的const積分メンバーを初期化できます
Dean P

回答:


67

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

つまり、これによりリンカーが複数の定義をソートする代わりに、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

1
これは以前はかなり可能だったようです。コンパイラを書く作業が難しくなっただけです。それは公正な声明ですか?
allyourcode 2013

10
@allyourcode:はい、いいえ。はい、コンパイラの記述が難しくなりました。しかし、いいえ。C++仕様の記述かなり難しくなったためです。
ジェリーコフィン

クラスメンバーを初期化する方法に違いはありますか:int x = 7; またはintx {7};?
mbaros

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は実装(コンパイラー、リンカー)にとって最も難しいケースであるため、そのままにしておきました。私のポイントは、その場合でもたとえばテンプレートを使用する場合など、すでに許可されているものよりも難しいことではありません。


C ++ 11の機能の多くは、コンパイラーに必要な機能や最適化がすでに含まれているという点で類似しているため、これが賛成票を獲得しなかったことを残念に思います。
アレックスコート

@AlexCourt私は最近この答えを書きました。質問とジェリーの答えは2012年のものです。だから私の答えはあまり注目されなかったのだと思います。
Paul Groke 2016

1
クラスで初期化できるのは
staticconst

8

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

ファイルを含めると、ファイルが含まれるだけで、初期化は無視されます。メンバーは、クラスをインスタンス化したときにのみ初期化されます。

言い換えると、初期化はまだコンストラクターと結びついており、表記法が異なり、より便利です。コンストラクターが呼び出されない場合、値は初期化されません。

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

これは、C ++ 11に関するStroustrup自身のFAQから明らかです。


Re "コンストラクターが呼び出されない場合、値は初期化されません":Y::c3質問のメンバーの初期化を回避するにはどうすればよいですか?私が理解してc3いるように、宣言で指定されたデフォルトをオーバーライドするコンストラクターがない限り、常に初期化されます。
ピーター-モニカを復活させる
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.