デフォルト、値、ゼロの初期化の混乱


88

値とデフォルトとゼロの初期化について非常に混乱しています。特に、異なる標準C ++ 03C ++ 11(およびC ++ 14)を採用する場合。

私はここで本当に良い答えを引用して拡張しようとしています値-/デフォルト-/ゼロ-初期化C ++ 98C ++ 03誰かが記入するのを手伝ってくれるなら多くのユーザーを助けるので、それをより一般的にしますいつ何が起こるかについての概要を把握するためにギャップが必要でしたか?

一言で言えば例による完全な洞察:

new演算子によって返されるメモリが初期化される場合もあれば、新しい型がPOD(単純な古いデータ)であるかどうか、またはPODメンバーを含み、を使用しているクラスであるかどうかに依存しない場合もあります。コンパイラーが生成したデフォルトのコンストラクター。

  • ではC ++ 1998:初期化の2種類があるゼロデフォルトの初期化は、
  • C ++ 2003の初期化の3種類、値初期化を加えました。
  • C ++ 2011 / C ++ 2014のみリストの初期化は、添加し、そしてための規則値- / default- /ゼロ初期化は、ビットを変更しました。

仮定:

struct A { int m; };                     
struct B { ~B(); int m; };               
struct C { C() : m(){}; ~C(); int m; };  
struct D { D(){}; int m; };             
struct E { E() = default; int m;}; /** only possible in c++11/14 */  
struct F {F(); int m;};  F::F() = default; /** only possible in c++11/14 */

C ++ 98コンパイラでは、次のことが発生するはずです。

  • new A -不確定な値(APOD)
  • new A()-ゼロ-初期化
  • new B -デフォルトの構成(B::m初期化されていない、BPODではない)
  • new B()-デフォルトの構成(B::m初期化されていません)
  • new C -デフォルトの構成(C::mゼロで初期化され、C非POD)
  • new C()-デフォルトの構成(C::mゼロで初期化されます)
  • new D -デフォルトの構成(D::m初期化されていない、DPODではない)
  • new D()-デフォルトの構成?D::m初期化されていません)

C ++ 03準拠のコンパイラでは、次のように動作するはずです。

  • new A -不確定な値(APOD)
  • new A() --value-initialize A。これはPODであるため、ゼロ初期化です。
  • new B -デフォルト-初期化(B::m初期化されないまま、B非POD)
  • new B() -値-初期化 Bユーザ定義とは対照的にそのデフォルトCTOR以降のすべてのフィールドをゼロに初期化コンパイラが生成されます。
  • new CC--default- 初期化します。これはデフォルトのctorを呼び出します。(C::mゼロで初期化され、C非PODです)
  • new C()C--value- 初期化します。これはデフォルトのctorを呼び出します。(C::mゼロで初期化されます)
  • new D -デフォルトの構成(D::m初期化されていない、DPODではない)
  • new D() -値-Dを初期化しますか?、デフォルトのctorを呼び出します(D::m初期化されていません)

イタリック値と?不確実です、これを修正するのを手伝ってください:-)

C ++ 11準拠のコンパイラでは、次のように動作するはずです。

??? (私がここから始めたら、とにかくうまくいかないので助けてください)

C ++ 14準拠のコンパイラでは、物事はそうのように動作するはずです: ??? (ここから始めたら、とにかくうまくいかないので助けてください) (回答に基づくドラフト)

  • new AA--default-コンパイラgenを 初期化します。ctor、(A::m初期化されていないまま)(APODです)

  • new A()--value -initialize A、これはゼロです- [dcl.init] / 8の2.ポイント以降の初期化

  • new BB--default-コンパイラgenを 初期化します。ctor、(B::m初期化されていないまま)(B非POD)

  • new B() -値の初期化Bユーザ定義とは対照的にそのデフォルトCTOR以降のすべてのフィールドをゼロに初期化コンパイラが生成されます。

  • new CC--default- 初期化します。これはデフォルトのctorを呼び出します。(C::mゼロで初期化され、C非PODです)

  • new C()C--value- 初期化します。これはデフォルトのctorを呼び出します。(C::mゼロで初期化されます)

  • new D -デフォルト-初期化DD::m初期化されていない、DPODではない)

  • new D()D--value -initialize 、デフォルトのctorを呼び出します(D::m初期化されていません)

  • new E --default- Ecompを呼び出すを初期化します。gen。ctor。(E::m初期化されておらず、Eは非PODです)

  • new E()--value -initialize E、ゼロ- [dcl.init] / 8のE2ポイント以降に初期化)

  • new F --default- Fcompを呼び出すを初期化します。gen。ctor。(F::m初期化されておらず、FPODではありません)

  • new F() -値初期化Fデフォルトの初期化は、 F 1.ポイント以降[dcl.init] / 8Fそれは、ユーザが宣言はなく、明示的にデフォルトまたはその最初の宣言に削除された場合CTOR機能は、ユーザが提供される。リンク


ここにそれの良い説明があります:en.cppreference.com/w/cpp/language/default_constructor
Richard Hodges

1
私の知る限り、これらの例ではC ++ 98とC ++ 03の違いしかありません。この問題は、N1161(そのドキュメントの新しいリビジョンがあります)およびCWG DR#178で説明されているようです。文言による新機能とPODの新しい仕様にC ++ 11の変更に必要なは、それが原因C ++ 11文言の欠陥にC ++ 14に再び変更しましたが、これらの場合に効果が変更されていません。
dyp 2015

3
退屈ですstruct D { D() {}; int m; };が、リストに含める価値があるかもしれません。
Yakk-Adam Nevraumont 2015

回答:


24

C ++ 14はnew、[expr.new] / 17で作成されたオブジェクトの初期化を指定します(C ++ 11では[expr.new] / 15であり、メモはメモではなく、当時の規範的なテキストでした)。

新しい表現型のオブジェクトを作成しT、以下のようにそのオブジェクトを初期化します。

  • 場合は、新たな初期化子が省略され、オブジェクトがあるデフォルトの初期化(8.5)。[注:初期化が実行されない場合、オブジェクトの値は不確定です。—エンドノート]
  • それ以外の場合、new-initializerは、直接初期化の8.5の初期化ルールに従って解釈されます

デフォルトの初期化は[dcl.init] / 7で定義されています(C ++ 11では/ 6であり、表現自体も同じ効果があります)。

デフォルトの初期化型のオブジェクトT手段:

  • 場合Tのために(おそらくCV修飾)クラスタイプ(条項9)、デフォルトコンストラクタ(12.1)であるT(と呼ばれた場合、初期化が病気に形成されているTデフォルトのコンストラクタまたはオーバーロード解決(曖昧さまたは13.3)の結果を有していません初期化のコンテキストから削除された、またはアクセスできない関数。
  • Tが配列型の場合、各要素はデフォルトで初期化されますます。
  • それ以外の場合、初期化は実行されません。

したがって、

  • new AA初期化しないデフォルトのコンストラクターが呼び出されるだけですm。不定値。についても同じである必要がありnew Bます。
  • new A() [dcl.init] / 11(C ++ 11では/ 10)に従って解釈されます。

    初期化子が空の括弧のセットであるオブジェクト、つまり()、は値で初期化されます。

    そして、[dcl.init] / 8(C ++ 11†では/ 7)について考えてみましょう。

    タイプのオブジェクトを値で初期化するには、次のことをT意味します。

    • もし Tないデフォルトコンストラクタ(12.1)またはユーザ提供または削除されたデフォルトのコンストラクタのいずれかと(おそらくCV修飾)クラスタイプ(条項9)であり、そのオブジェクトは、デフォルト初期化です。
    • もし Tユーザ提供または削除デフォルトコンストラクタなしで(おそらくCV修飾)クラスタイプは、オブジェクトはゼロで初期化され、デフォルト初期化のためのセマンティック制約がチェックされ、そしてTは非自明なデフォルトコンストラクタを持つ場合、オブジェクトはデフォルトで初期化されます。
    • 場合T、アレイ型で、各要素の値に初期化です。
    • それ以外の場合、オブジェクトはゼロで初期化されます。

    したがって、new A()ゼロで初期化しますm。そして、これはとと同等である必要がAありBます。

  • new Cおよびnew C()(Cは、ユーザ提供のデフォルトコンストラクタを持っている!)、デフォルトの初期化最後の引用からの最初の箇条書きが適用されているので、オブジェクトを再度します。しかし、明らかに、mどちらの場合もコンストラクターで初期化されます。


†この段落のC ++ 11の表現は少し異なりますが、結果は変わりません。

タイプのオブジェクトを値で初期化するには、次のことをT意味します。

  • 場合Tユーザ提供コンストラクタ(12.1)と(おそらくCV修飾)クラスタイプ(条項9)であり、その後のデフォルトコンストラクタがT 呼び出され(そしてTは全くアクセスデフォルトコンストラクタを持たない場合、初期化が病気に形成されています)。
  • 場合はT、ユーザーが提供するコンストラクタなし(おそらくCV修飾)非組合クラス型である場合、オブジェクトはゼロに初期化され、場合などTの暗黙的に宣言されたデフォルトコンストラクタは、コンストラクタが呼び出されることを、非自明です。
  • もし T、アレイ型で、各要素の値に初期化です。
  • それ以外の場合、オブジェクトはゼロで初期化されます。

@Gabrielはそうではありません。
コロンボ2015

ああ、あなたは主にc ++ 14について話していて、c ++ 11の参照は括弧内に示されています
Gabriel

@ガブリエル正解。つまり、C ++ 14が最新の標準であるため、それが前面に出ています。
コロンボ2015

1
標準間で初期化ルールをトレースしようとすることの厄介な点は、公開されたC ++ 14標準とC ++ 11標準の間の多くの変更(ほとんど?すべて?)がDRを介して行われたことであり、事実上のC ++ 11も同様です。 。そして、C ++ 14以降のDRもあります...–
TC

@Columboなぜstruct A { int m; }; struct C { C() : m(){}; int m; };異なる結果が生成されるのか、そもそもAのmが初期化される原因はまだわかりません。私が行った実験専用のスレッドを開きました。問題を明確にするために、そこでのご意見に感謝いたします。おかげstackoverflow.com/questions/45290121/...
darkThoughts

12

次の回答は、C ++ 98およびC ++ 03のリファレンスとして機能する回答https://stackoverflow.com/a/620402/977038を拡張したものです。

答えを引用する

  1. C ++ 1998には、ゼロとデフォルトの2種類の初期化があります。
  2. C ++ 2003では、3番目のタイプの初期化で、値の初期化が追加されました。

C ++ 11(n3242を参照)

イニシャライザー

8.5イニシャライザ[dcl.init]指定する変数PODまたは非PODとしてのいずれかで初期化することができるブレース、または等しく、初期のいずれかとすることができる ブレース-INIT-リスト又は初期化子句は集約と呼ぶ ブレース・オア・イコール初期化子またはusing (expression-list)。C ++ 11より前は、(expression-list)またはinitializer-clauseのみがサポートされていましたが、initializer-clauseは、C ++ 11の場合よりも制限されていました。C ++ 11では、initializer-clauseは、assignment-expressionとは別にbraced-init-listをサポートするようになりました。C ++ 03のように。次の文法は、サポートされている新しい句をまとめたものです。太字の部分は、C ++ 11標準で新しく追加されています。

イニシャライザ:中
    括弧または等しい初期化子
    (式リスト)中
括弧または等しい初期化子:
    =初期化子句中括弧
    付き
初期化リスト初期化子句:
    割り当て式中括弧付き初期化
    リスト
初期化子リスト:
    初期化子句... opt
    initializer-list、initializer-clause ... opt **
braced-init-list:
    {initializer-list、opt}
    {}

初期化

C ++ 03と同様に、C ++ 11でも3つの形式の初期化がサポートされています


注意

太字で強調表示されている部分はC ++ 11で追加され、取り消し線が引かれている部分はC ++ 11から削除されています。

  1. 初期化子タイプ:8.5.5 [dcl.init] _zero-initialize_

以下の場合に実施

  • 静的またはスレッドの保存期間を持つオブジェクトはゼロで初期化されます
  • 配列要素よりも初期化子の数が少ない場合、明示的に初期化されていない各要素はゼロで初期化されます。
  • 値の初期化中に、Tがユーザー提供のコンストラクターのない(おそらくcv修飾された)非共用体クラス型である場合、オブジェクトはゼロで初期化されます。

タイプTのオブジェクトまたは参照をゼロ初期化するということは、次のことを意味します。

  • Tがスカラー型(3.9)の場合、オブジェクトは値0(ゼロ)に設定され、整数定数式と見なされ、Tに変換されます。
  • Tが(おそらくcv修飾された)非ユニオンクラスタイプの場合、各非静的データメンバーと各基本クラスサブオブジェクトはゼロで初期化され、パディングはゼロビットに初期化されます。
  • Tが(おそらくcv修飾された)共用体型である場合、オブジェクトの最初の非静的名前付きデータメンバーはゼロで初期化され、パディングはゼロビットに初期化されます。
  • Tが配列型の場合、各要素はゼロで初期化されます。
  • Tが参照型の場合、初期化は実行されません。

2.イニシャライザータイプ:8.5.6 [dcl.init] _default-initialize_

以下の場合に実施

  • new-initializerを省略すると、オブジェクトはデフォルトで初期化されます。初期化が実行されない場合、オブジェクトの値は不確定です。
  • オブジェクトに初期化子が指定されていない場合、静的またはスレッドの保管期間を持つオブジェクトを除いて、オブジェクトはデフォルトで初期化されます。
  • 基本クラスまたは非静的データメンバーがコンストラクター初期化子リストに記載されておらず、そのコンストラクターが呼び出された場合。

タイプTのオブジェクトをデフォルトで初期化するには、次のことを意味します。

  • Tが(おそらくcv修飾された) 非PODクラスタイプである場合(第9節)、Tのデフォルトコンストラクターが呼び出されます(Tにアクセス可能なデフォルトコンストラクターがない場合、初期化は不正な形式になります)。
  • Tが配列型の場合、各要素はデフォルトで初期化されます。
  • それ以外の場合、初期化は実行されません。

C ++ 11までは、初期化子が使用されていない場合、自動保存期間を持つ非PODクラスタイプのみがデフォルトで初期化されていると見なされていました。


3.初期化子タイプ:8.5.7 [dcl.init] _value-initialize_

  1. 初期化子が空の括弧のセット、つまり()または中括弧{}であるオブジェクト(名前のない一時、名前付き変数、動的ストレージ期間、または非静的データメンバー)の場合

タイプTのオブジェクトを値初期化するということは、次のことを意味します。

  • Tがユーザー提供のコンストラクター(12.1)を持つ(おそらくcv修飾された)クラスタイプ(条項9)である場合、Tのデフォルトコンストラクターが呼び出されます(Tにアクセス可能なデフォルトコンストラクターがない場合、初期化は不正な形式になります) ;
  • Tが(おそらくcv修飾された)非ユニオンクラスタイプであり、ユーザー提供のコンストラクターがない場合、Tのすべての非静的データメンバーと基本クラスコンポーネントは値で初期化されます。 次に、オブジェクトはゼロで初期化され、Tの暗黙的に宣言されたデフォルトのコンストラクターが自明でない場合、そのコンストラクターが呼び出されます。
  • Tが配列型の場合、各要素は値で初期化されます。
  • それ以外の場合、オブジェクトはゼロで初期化されます。

要約すると

規格からの関連する引用は太字で強調表示されています

  • new A:default-initialize(A :: mを初期化しないままにします)
  • new A():値が初期化された候補にはユーザー提供または削除されたデフォルトコンストラクターがないため、Aをゼロ初期化します。Tがユーザー提供のコンストラクターのない(おそらくcv修飾された)非ユニオンクラスタイプである場合、オブジェクトはゼロで初期化され、Tの暗黙的に宣言されたデフォルトコンストラクターが自明でない場合、そのコンストラクターが呼び出されます。
  • 新しいB:デフォルト-初期化(B :: mを初期化しないままにする)
  • new B():value-すべてのフィールドをゼロで初期化するBを初期化します。Tが(おそらくcv修飾された)クラス型(第9節)であり、ユーザー提供のコンストラクター(12.1)である場合、Tのデフォルトコンストラクターが呼び出されます。
  • new C:default-デフォルトのctorを呼び出すCを初期化します。Tが(おそらくcv修飾された)クラス型である場合(第9節)、Tのデフォルトコンストラクターが呼び出されます。さらに、new-initializerが省略された場合、オブジェクトはデフォルトで初期化されます。
  • new C():value-デフォルトのctorを呼び出すCを初期化します。Tが(おそらくcv修飾された)クラス型(第9節)であり、ユーザー提供のコンストラクター(12.1)である場合、Tのデフォルトコンストラクターが呼び出されます。さらに、初期化子が空の括弧のセットであるオブジェクト、つまり()は、値で初期化されます。

0

C ++ 11では、少なくともコンパイラの実装に従って、C ++ 14の質問で言及されているすべてが正しいことを確認できます。

これを確認するために、次のコードをテストスイートに追加しました。私はでテスト-std=c++11 -O3GCC 7.4.0、GCC 5.4.0、クラン10.0.1、およびVS 2017、およびパス以下のすべてのテストで。

#include <gtest/gtest.h>
#include <memory>

struct A { int m;                    };
struct B { int m;            ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){};             };
struct E { int m; E() = default;     };
struct F { int m; F();               }; F::F() = default;

// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }

// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)

TEST(TestZero, StackDefaultInitialization)
{
    { FILL; A a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; B a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; C a; EXPECT_EQ(a.m, 0); }
    { FILL; D a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackValueInitialization)
{
    { FILL; A a = A(); EXPECT_EQ(a.m, 0); }
    { FILL; B a = B(); EXPECT_EQ(a.m, 0); }
    { FILL; C a = C(); EXPECT_EQ(a.m, 0); }
    { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a = E(); EXPECT_EQ(a.m, 0); }
    { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackListInitialization)
{
    { FILL; A a{}; EXPECT_EQ(a.m, 0); }
    { FILL; B a{}; EXPECT_EQ(a.m, 0); }
    { FILL; C a{}; EXPECT_EQ(a.m, 0); }
    { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a{}; EXPECT_EQ(a.m, 0); }
    { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, HeapDefaultInitialization)
{
    { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapValueInitialization)
{
    { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapListInitialization)
{
    { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

UB!言及されている場所は未定義の動作であり、実際の動作は多くの要因に依存する可能性があります(a.m42、0、またはその他のゴミに等しい場合があります)。~UB言及されている場所も理論的には未定義の動作ですが、実際には、新しい配置を使用しているため、a->m42以外のものと等しくなる可能性はほとんどありません。

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