c++ - clang version




默認值和零初始化混亂 (2)

我對value-&default-&zero-initialization感到非常困惑。 尤其是當他們採用不同的標準 C ++ 03 C ++ 11 (和 C ++ 14 )時。

我在這裡引用並嘗試擴展一個很好的答案 Value- / Default- / Zero-Init C ++ 98 C ++ 03 ,使其更通用,因為如果有人可以幫助填寫,它將對很多用戶有所幫助需要差距才能對何時會發生什麼有一個很好的了解?

簡而言之,通過示例全面了解:

有時new運算符返回的內存將被初始化,有時它並不取決於您要更新的類型是 POD(普通舊數據) ,還是它是包含POD成員並且正在使用a的類。編譯器生成的默認構造函數。

  • C ++ 1998中 ,有兩種初始化類型: 初始化和 默認初始化
  • C ++ 2003中 ,第三種初始化類型是 值初始化
  • C ++ 2011 / C ++ 2014中, 僅添加了 列表初始化 ,並且 value- / default- / zero-initialization 的規則有所更改。

假設:

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 不確定值( A 是POD)
  • new A() -零初始化
  • new B 默認構造( B::m 未初始化, B 為非POD)
  • new B() -默認構造( B::m 未初始化)
  • new C 默認構造( C::m 為零初始化, C 為非POD)
  • new C() -默認構造( C::m 初始化為零)
  • new D 默認構造( D::m 未初始化, D 為非POD)
  • new D() - 默認構造? D::m 未初始化)

在符合C ++ 03的編譯器中,事情應該像這樣運行:

  • new A 不確定值( A 是POD)
  • new A() -值初始化 A ,因為它是一個POD,所以為零初始化。
  • new B 默認初始化(葉子 B::m 未初始化, B 是非POD)
  • new B() -值初始化 B ,該值將所有字段歸零,因為它的默認ctor是由編譯器生成的,而不是由用戶定義的。
  • new C 默認初始化 C ,它將調用默認ctor。 ( C::m 為零初始化, C 為非POD)
  • new C() -值初始化 C ,它將調用默認的ctor。 ( C::m 為零初始化)
  • new D 默認構造( D::m 未初始化, D 為非POD)
  • new D() - 值是否初始化D? ,它將調用默認的ctor( D::m 未初始化)

斜體值和? 有不確定性,請幫助糾正此問題:-)

在符合C ++ 11的編譯器中,事情應該像這樣運行:

??? (如果我從這裡開始,請幫忙,否則還是會出錯)

在符合C ++ 14的編譯器中,事情應該像這樣工作: (如果我從這裡開始,請幫忙,否則還是會出錯) (根據答案起草)

  • new A 默認初始化 A ,編譯器生成。 ctor,(未初始化葉子 A::m )( A 是POD)
  • new A() -值初始化 A ,從2開始在零點初始化 [dcl.init] / 8

  • new B 默認初始化 B ,編譯器生成。 ctor,(未初始化葉子 B::m )( B 是非POD)

  • new B() -值初始化 B ,該值將所有字段歸零,因為它的默認ctor是由編譯器生成的,而不是由用戶定義的。
  • new C 默認初始化 C ,它將調用默認ctor。 ( C::m 為零初始化, C 為非POD)
  • new C() -值初始化 C ,它將調用默認的ctor。 ( C::m 為零初始化)
  • new D 默認初始化 DD::m 未初始化, D 為非POD)
  • new D() -值初始化 D ,它調用默認的ctor( D::m 未初始化)
  • new E 默認初始化 E ,它調用comp。 gen。 ctor。 ( E::m 未初始化,E為非POD)
  • new E() -值初始化 E ,從 [dcl.init] / 8中的 2點開始對 E 進行零初始化
  • new F 默認初始化 F ,它調用comp。 gen。 ctor。 ( F::m 未初始化, F 為非POD)
  • new F() -值初始化 F ,從 [dcl.init] / 8中的 1點開始 默認初始化 FF ctor函數是用戶提供的,如果它是用戶聲明的,並且未在其第一個函數上顯式默認或刪除)聲明。 Link

C ++ 14在[expr.new] / 17中指定了用 new 創建的對象的初始化(在C ++ 11中為[expr.new] / 15,並且該註釋不是註釋,而是當時的規範文本):

創建一個 T 類型對象的 new表達式 按如下方式初始化該對象:

  • 如果省略了 new-initializer ,則 默認初始化 該對象(8.5)。 [ 注意: 如果不執行任何初始化,則該對象具有不確定的值。 —尾註 ]
  • 否則,將根據8.5的初始化規則對 new-initializer 進行解釋以進行 直接初始化

默認初始化在[dcl.init] / 7中定義(在C ++ 11中為/ 6,其措詞本身俱有相同的效果):

默認初始化 類型 T 的對象意味著:

  • 如果 T 是(可能是cv限定的)類類型(第9條),則調用 T 的默認構造函數(12.1)(如果 T 沒有默認構造函數或重載分辨率(13.3),則初始化格式不正確)。模糊性或從初始化上下文中刪除或無法訪問的函數中);
  • 如果 T 是數組類型,則每個元素都將 默認初始化
  • 否則,不執行初始化。

從而

  • new A 僅導致 A 的默認構造函數被調用,而不初始化 m 。 不確定的價值。 對於 new B 應該相同。
  • 根據[dcl.init] / 11(在C ++ 11中為/ 10 new A() 解釋 new A() ):

    一個其初始值設定項是一個空括號集合(即 () 應進行值初始化。

    現在考慮[dcl.init] / 8(在C ++ 11†中為/ 7):

    值初始化 類型 T 的對象意味著:

    • 如果 T 是一個(可能是cv限定的)類類型(第9條),沒有默認構造函數(12.1)或用戶提供或刪除的默認構造函數,則該對象為默認初始化的;
    • 如果 T 是(可能是cv限定的)類類型,而沒有用戶提供或刪除的默認構造函數,則將該對像初始化為零,並檢查默認初始化的語義約束​​,並且T是否具有非平凡的默認構造函數,該對像是默認初始化的;
    • 如果 T 是數組類型,則每個元素都將值初始化;
    • 否則,將對像初始化為零。

    因此, new A() 將對 m 零初始化。 這對於 AB 是等效的。

  • new Cnew C() 將再次默認初始化該對象,因為使用了最後一個引號的第一個項目符號點(C具有用戶提供的默認構造函數!)。 但是,很明顯,在兩種情況下,現在都在構造函數中初始化了 m

†好,此段在C ++ 11中的措詞略有不同,這不會改變結果:

值初始化 類型 T 的對象意味著:

  • 如果 T 是具有用戶提供的構造函數(12.1)的(可能是cv限定的)類類型(第9條),則調用 T 的默認構造函數(如果T沒有可訪問的默認構造函數,則初始化格式不正確) ;
  • 如果 T 是(可能是cv限定的)不具有用戶提供的構造函數的非工會類類型,則該對象為零初始化,並且如果 T 的隱式聲明的默認構造函數非平凡,則調用該構造函數。
  • 如果 T 是數組類型,則每個元素都將值初始化;
  • 否則,將對像初始化為零。

以下答案擴展了答案 https://.com/a/620402/977038 ,它將作為C ++ 98和C ++ 03的參考

引用答案

  1. 在C ++ 1998中,有兩種​​初始化類型:零和默認
  2. 在C ++ 2003的第三種初始化類型中,添加了值初始化。

C ++ 11(參考n3242)

初始化器

8.5初始化程序[dcl.init]指定可以將變量POD或非POD初始化為 大括號或相等初始化程序 ,可以將其 初始化 大括號 初始 列表 ,也可以將 初始化子句 統稱為 括號或相等程序。 初始化程序 或使用 (expression-list) 。 在C ++ 11之前,僅支持 (expression-list) initializer-clause ,儘管與C ++ 11中的限制相比, initializer-clause 受到更多限制。 在C ++ 11中,除了C ++ 03中的 賦值表達式 外, 初始化子句 現在支持 braced-init-list 。 以下語法總結了新的support子句,其中在C ++ 11標準中新添加了粗體部分。

初始值設定項:
大括號或相等的初始化程序
(表達式列表)
大括號或相等的初始化程序:
=初始化子句
括號初始化列表
初始化子句:
任務表達
括號初始化列表
初始化列表:
初始化子句... opt
初始化列表,初始化子句... opt **
括號初始化列表:
{initializer-list,opt}
{}

初始化

與C ++ 03一樣,C ++ 11仍支持三種初始化形式

注意

粗體突出顯示的部分已在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是數組類型,則每個元素都將值初始化;
  • 否則,將對像初始化為零。

所以總結一下

注意 標準中的相關報價以 粗體 突出 顯示

  • 新A:默認初始化(未初始化A :: m)
  • new A():零初始化A,因為值初始化的候選對像沒有用戶提供或刪除的默認構造函數。 如果T是一個沒有用戶提供的構造函數的(可能是cv限定的)非工會類類型,則該對象將初始化為零,並且,如果T隱式聲明的默認構造函數非平凡,則將調用該構造函數。
  • 新B:默認初始化(葉子B :: m未初始化)
  • new B():將B值初始化,將所有字段清零; 如果T是具有用戶提供的構造函數(12.1)的(可能是cv限定的)類類型(第9條),則調用T的默認構造函數
  • new C:默認初始化C,該C調用默認ctor。 如果T是(可能是cv限定的)類類型(第9條),則調用T的默認構造函數 ,此外,如果省略new-initializer,則該對象將被默認初始化
  • new C():值初始化C,它將調用默認的ctor。 如果T是具有用戶提供的構造函數(12.1)的(可能是cv限定的)類類型(第9條),則將調用T的默認構造函數。 此外, 其初始化程序是一個空括號集合(即())的對象應進行值初始化。






c++98