C ++がconstオブジェクトをデフォルトで構築するためにユーザー提供のデフォルトコンストラクターを必要とするのはなぜですか?


99

C ++標準(セクション8.5)は次のように述べています。

プログラムがconst修飾型Tのオブジェクトのデフォルト初期化を要求する場合、Tはユーザー提供のデフォルトコンストラクターを持つクラス型になります。

どうして?この場合、ユーザー提供のコンストラクターが必要な理由は考えられません。

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}

2
宣言したが定義/初期化しなかったので、例ではこの行は必要ないようです(ideone.com/qqiXRを参照)が、そうしたa場合でもgcc-4.3.4はそれを受け入れます(ideone.com/uHvFSを参照)
Ray Toal 2011

上記の例では、を宣言および定義していますa。コモーは、行がコメント化されている場合、「const変数 "a"にはイニシャライザが必要です-クラス "A"には明示的に宣言されたデフォルトコンストラクタがありません」というエラーを生成します。
Karu


4
これはC ++ 11で修正され、次のように記述できますconst A a{}:)
Howard Lovatt

回答:


10

これは(標準のすべてのバージョンに対して)不具合と見なされ、Core Working Group(CWG)不具合253によって解決されました。http://eel.is/c++draft/dcl.init#7の標準状態の新しい表現

Tのdefault-initializationがユーザー提供のTのコンストラクター(基本クラスから継承されていない)を呼び出す場合、または次の場合、クラス型Tはconst-default-constructibleです。

  • Tの各直接非バリアント非静的データメンバーMには、デフォルトのメンバー初期化子があります。MがクラスタイプX(またはその配列)の場合、Xはconst-default-constructibleです。
  • Tが少なくとも1つの非静的データメンバーを持つユニオンである場合、正確に1つのバリアントメンバーにデフォルトメンバー初期化子があります。
  • Tが共用体でない場合、少なくとも1つの非静的データメンバー(存在する場合)を持つ匿名の共用メンバーごとに、1つの非静的データメンバーにデフォルトのメンバー初期化子があります。
  • Tの各潜在的に構築された基本クラスはconst-default-constructibleです。

プログラムがconst修飾型Tのオブジェクトのデフォルト初期化を要求する場合、Tはconst-default-constructibleクラス型またはその配列になります。

この言葉遣いは、本質的に明らかなコードが機能することを意味します。すべてのベースとメンバーを初期化する場合A const a;、コンストラクターのスペルや方法に関係なく言うことができます。

struct A {
};
A const a;

gccはこれを4.6.4以降受け入れています。clangは3.9.0以降、これを受け入れています。Visual Studioもこれを受け入れます(少なくとも2017年には、もっと早いかどうかはわかりません)。


3
しかし、新しい表現がまだ「ユーザー宣言」ではなく「ユーザー提供」を示しているため、これはまだstruct A { int n; A() = default; }; const A a;許可struct B { int n; B() {} }; const B b;されず、委員会が明示的にデフォルト設定されたデフォルトコンストラクターをこのDRから除外することを選択した理由を頭にかき残し、初期化されていないメンバーを持つconstオブジェクトが必要な場合、クラスは重要です。
オクタリスト

1
興味深いですが、私が遭遇したエッジケースはまだあります。MyPODPODされstructstatic MyPOD x;適切メンバ変数(複数可)を設定する(?ことが正しいものである)ゼロ初期化に依存する- -コンパイルし、しかしstatic const MyPOD x;ありません。それが修正される可能性はありますか?
ジョシュアグリーン

66

その理由は、クラスにユーザー定義のコンストラクターがない場合、それはPODになる可能性があり、PODクラスはデフォルトで初期化されないためです。では、初期化されていないPODのconstオブジェクトを宣言する場合、それをどのように使用しますか?したがって、オブジェクトが実際に役立つように、標準はこのルールを適用すると思います。

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

しかし、クラスを非PODにした場合:

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

PODと非PODの違いに注意してください。

ユーザー定義コンストラクタは、クラスを非PODにする1つの方法です。これを行うにはいくつかの方法があります。

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

nonPOD_Bはユーザー定義コンストラクタを定義していないことに注意してください。コンパイルします。それはコンパイルされます:

そして、仮想関数をコメントすると、期待どおりにエラーが発生します。


ええと、あなたはその箇所を誤解していると思います。それは最初にこれを言います(§8.5/ 9):

オブジェクトに初期化子が指定されておらず、オブジェクトが(おそらくcvで修飾された)非PODクラス型(またはその配列)である場合、オブジェクトはデフォルトで初期化されます。[...]

非PODクラス、おそらくcv修飾型について話します。つまり、初期化子が指定されていない場合、非PODオブジェクトはデフォルトで初期化されます。そして、デフォルトで初期化されるものは何ですか?非PODの場合、仕様には(§8.5/ 5)とありますが、

タイプTのオブジェクトをデフォルトで初期化するとは、次のことを意味します
。— Tが非PODクラスタイプ(9節)の場合、Tのデフォルトコンストラクターが呼び出されます(Tにアクセス可能なデフォルトコンストラクターがない場合、初期化は正しくありません)。

Tのデフォルトコンストラクターについて話しているだけで、ユーザー定義またはコンパイラーが生成したものは関係ありません。

これまでに問題がなければ、次に仕様が言うことを理解してください((8.5 / 9)、

[...]; オブジェクトがconst修飾型の場合、基本となるクラス型にはユーザー宣言のデフォルトコンストラクターが必要です。

このテキストは暗示ので、プログラムが病気に形成される場合、オブジェクトがあるのconst修飾 PODのタイプ、および(PODが初期化デフォルトではありませんされているため)初期化子が指定されていません。

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

ちなみに、これは非PODであり、デフォルトで初期化できるため、正常コンパイルされます


1
最後の例はコンパイルエラーだと思います。nonPOD_Bユーザー指定のデフォルトコンストラクターconst nonPOD_B b2がないため、この行は許可されていません。
Karu、

1
クラスを非PODにするもう1つの方法は、PODではないデータメンバーをクラスに与えることです(たとえばB、質問の私の構造体)。ただし、その場合でもユーザー指定のデフォルトコンストラクターが必要です。
Karu、

「プログラムがconst修飾型Tのオブジェクトのデフォルトの初期化を要求する場合、Tはユーザー提供のデフォルトコンストラクターを持つクラス型でなければなりません。」
Karu

@かる:読んだよ。仕様には、constコンパイラーによって生成されたdefault-constructorを呼び出すことによって非PODオブジェクトを初期化できるようにする他の節があるようです。
Nawaz、2011

2
あなたのideoneリンクは壊れているようで、§8.5はPODについてまったく言及していないため、この回答をC ++ 11/14に更新できればすばらしいと思います。
オクタリスト2014年

12

私の部分では純粋な推測ですが、他のタイプにも同様の制限があることを考慮してください:

int main()
{
    const int i; // invalid
}

したがって、このルールは一貫しているだけでなく、(再帰的に)単一化されたconst(サブ)オブジェクトも防止します。

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

質問の反対側(デフォルトコンストラクターを持つ型に許可する)に関しては、ユーザー提供のデフォルトコンストラクターを持つ型は、構築後は常にある意味のある状態になるはずだと考えています。ルールは次のように許可されていることに注意してください。

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

次に、「少なくとも1つのメンバーをユーザー指定のデフォルトコンストラクターで適切に初期化する必要がある」などのルールを作成できますが、マーフィーからの保護に費やす時間が多すぎます。C ++は特定の点でプログラマーを信頼する傾向があります。


しかし、を追加することA(){}で、エラーは解消されるため、何も防止されません。ルールは再帰的には機能しません- X(){}その例では必要ありません。
Karu、

2
まあ、少なくとも、コンストラクタを追加するために、プログラマを強制することで、彼は問題に分の考えを与えると、おそらく非自明な1を思い付くことを余儀なくされる
アルネ

@Karu質問の半分しか答えなかった-修正された:)
Luc Danton

4
@arne:唯一の問題は、それが間違ったプログラマーであるということです。クラスをインスタンス化しようとする人は、彼が望むすべてのことを問題に与える可能性がありますが、クラスを変更できない場合があります。クラスの作成者はメンバーについて考え、暗黙のデフォルトコンストラクターによってすべてメンバーが賢明に初期化されていることを確認したため、メンバーを追加しませんでした。
Karu

3
私が標準のこの部分から取ったのは、「誰かがいつかconstインスタンスを作成したい場合に備えて、常に非PODタイプのデフォルトコンストラクターを宣言すること」です。それは少しやり過ぎのようです。
Karu

3

ミーティングC ++ 2018でTimur Doumlerの講演を見ていたときに、標準がここでユーザー提供のコンストラクタではなく、ユーザー提供のコンストラクタを必要とする理由がようやくわかりました。これは、値の初期化のルールに関係しています。

2つのクラスを考えてみましょう:A持っているユーザーが宣言し、コンストラクタをB持っているユーザー提供のコンストラクタを:

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

一見すると、これらの2つのコンストラクタは同じように動作すると思います。ただし、デフォルトの初期化のみが同じように動作する一方で、値の初期化がどのように異なる動作をするかを確認してください。

  • A a;デフォルトの初期int x化です。メンバーは初期化されていません。
  • B b;デフォルトの初期int x化です。メンバーは初期化されていません。
  • A a{};値の初期です。メンバーint xゼロで初期化されます
  • B b{};値の初期化です:メンバーint xは初期化されていません。

追加するとどうなるか見てみましょうconst

  • const A a;デフォルトの初期化です。質問で引用されているルールのため、これは形式正しくありません。
  • const B b;デフォルトの初期int x化です。メンバーは初期化されていません。
  • const A a{};値の初期です。メンバーint xゼロで初期化されます
  • const B b{};値の初期化です:メンバーint xは初期化されていません。

初期化されていないconstスカラー(int xメンバーなど)は役に立たないでしょう。書き込みconstは(不正であるため)不正であり、読み取りはUB(不確定な値を保持しているため)です。ためにあなたを強制することで、そのような事を作るから、このルールを防ぎますので、どちらかの初期化子の追加ユーザー提供のコンストラクタを追加することによって、危険な行動にオプトイン。

[[uninitialized]]オブジェクトを意図的に初期化していないときにコンパイラーに伝えるような属性があるといいと思います。そうすれば、このコーナーケースを回避するために、クラスを自明にデフォルトで構築できなくすることを強制されることはありません。この属性は実際に提案ていますが、他のすべての標準属性と同様に、規範的な動作を義務付けるものではなく、コンパイラへのヒントにすぎません。


1

おめでとうございます。あなたは、const意味のある初期化子を持たない宣言のためにユーザー定義のコンストラクターが必要ないケースを発明しました。

さて、あなたのケースをカバーするが、それでも違法であるはずのケースを違法にするルールの合理的な言い換えを思いつくことができますか?5段落または6段落未満ですか?それがどのような状況でどのように適用されるべきかは簡単で明白ですか?

あなたが作成した宣言が意味をなすことを可能にするルールを思いつくことは本当に難しいと思います、そしてコードを読むときに人々にとって意味のある方法でルールが適用されることを確実にすることは確かです。私は、ほとんどの場合に正しいことであるやや制限的なルールを、理解と適用が難しい非常に微妙で複雑なルールよりも好みます。

問題は、ルールをより複雑にするべき説得力のある理由があるかどうかです。ルールがより複雑な場合に、より簡単に記述できる、そうでなければ記述または理解するのが非常に難しいコードはありますか?


1
私が提案する表現は次のとおりです。「プログラムがconst修飾型Tのオブジェクトのデフォルトの初期化を要求する場合、Tは非PODクラス型になります。」これはconst POD x;違法であるようconst int x;に違法になります(これはPODには役に立たないので理にかなっています)がconst NonPOD x;合法です(有効なコンストラクタ/デストラクタを含むサブオブジェクトがあるか、または有用なコンストラクタ/デストラクタ自体があるので理にかなっています) 。
Karu、2011

@カル-その言葉遣いはうまくいくかもしれません。私はRFC規格に慣れているので、「Tでなければならない」は「Tでなければならない」と読むべきだと感じています。しかし、はい、それはうまくいくかもしれません。
全面的2011

@Karu-struct NonPod {int i;はどうですか?virtual void f(){}}?const NonPod xにするのは意味がありません。法的。
gruzovator 2013年

1
@gruzovatorユーザーが宣言した空のデフォルトコンストラクターがある場合、それ以上の意味があるでしょうか。私の提案は、規格の無意味な要件を削除しようとするだけです。それがあってもなくても、意味のないコードを書く方法は無限にあります。
Karu

1
@カル同意します。標準のこのルールのため、ユーザー定義の空のコンストラクターを持たなければならない多くのクラスがあります。私はgccの振る舞いが好きです。たとえばstruct NonPod { std::string s; }; const NonPod x;、NonPodの場合はエラーが発生しますstruct NonPod { int i; std::string s; }; const NonPod x;
gruzovator
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.