ค่าเริ่มต้นค่าและความยุ่งเหยิงในการเริ่มต้นเป็นศูนย์


89

ฉันสับสนมากเกี่ยวกับ value- & default- & zero-initialization และโดยเฉพาะอย่างยิ่งเมื่อพวกเขาเริ่มใช้มาตรฐานC ++ 03และC ++ 11ที่แตกต่างกัน(และC ++ 14 )

ฉันกำลังอ้างถึงและพยายามขยายคำตอบที่ดีจริงๆValue- / Default- / Zero- Init C ++ 98และC ++ 03ที่นี่เพื่อให้กว้างขึ้นเนื่องจากจะช่วยผู้ใช้จำนวนมากหากมีใครช่วยกรอก จำเป็นต้องมีช่องว่างเพื่อให้มีภาพรวมที่ดีว่าจะเกิดอะไรขึ้นเมื่อใด

ข้อมูลเชิงลึกทั้งหมดโดยตัวอย่างโดยสรุป:

บางครั้งหน่วยความจำที่ส่งคืนโดยตัวดำเนินการใหม่จะถูกเตรียมใช้งานและบางครั้งก็ไม่ขึ้นอยู่กับว่าประเภทที่คุณกำลังสร้างขึ้นใหม่นั้นเป็นPOD (ข้อมูลเก่าธรรมดา)หรือถ้าเป็นคลาสที่มีสมาชิก POD และใช้ ตัวสร้างเริ่มต้นที่สร้างโดยคอมไพเลอร์

  • ในC ++ 1998มีการกำหนดค่าเริ่มต้น 2 ประเภท: zero-และdefault-initialization
  • ในC ++ 2003เป็นประเภทที่ 3 ของการเริ่มต้นการกำหนดค่าเริ่มต้นถูกเพิ่ม
  • ในC ++ 2011 / C ++ 2014มีการเพิ่มเฉพาะรายการเริ่มต้นและกฎสำหรับค่า- / ค่าเริ่มต้น / ศูนย์ - เริ่มต้นเปลี่ยนแปลงเล็กน้อย

สมมติ:

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 - default-initializes 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คอมไพเลอร์ gen ctor, (leavs A::muninitialized) ( Aคือ POD)

  • new A() - ค่าเริ่มต้นAซึ่งเป็นศูนย์เริ่มต้นตั้งแต่ 2 จุดใน[dcl.init] / 8

  • new B - ค่าเริ่มต้นเริ่มต้นBคอมไพเลอร์ gen ctor, (leavs B::muninitialized) ( Bไม่ใช่ POD)

  • new B() - ค่าเริ่มต้นBซึ่งเป็นศูนย์เริ่มต้นฟิลด์ทั้งหมดเนื่องจาก ctor เริ่มต้นเป็นคอมไพเลอร์ที่สร้างขึ้นเมื่อเทียบกับที่ผู้ใช้กำหนด

  • new C - default-initializes Cซึ่งเรียก ctor เริ่มต้น ( C::mเป็นศูนย์เริ่มต้นCไม่ใช่ POD)

  • new C() - ค่าเริ่มต้นCซึ่งเรียก ctor เริ่มต้น ( C::mเป็นศูนย์เริ่มต้น)

  • new D - ค่าเริ่มต้นเริ่มต้นD( D::mไม่ได้กำหนดค่าเริ่มต้นDไม่ใช่ POD)

  • new D() - ค่าเริ่มต้นDซึ่งเรียก ctor เริ่มต้น ( D::mไม่ได้กำหนดค่าเริ่มต้น)

  • new E - default-initializes Eซึ่งเรียกคอมพ์ gen. ctor. ( E::mไม่ได้กำหนดค่าเริ่มต้น E ไม่ใช่ POD)

  • new E() - ค่าเริ่มต้นEซึ่งเป็นศูนย์เริ่มต้นEตั้งแต่ 2 จุดใน[dcl.init] / 8 )

  • new F - default-initializes Fซึ่งเรียกคอมพ์ gen. ctor. ( F::mไม่ได้กำหนดFค่าเริ่มต้นไม่ใช่ POD)

  • new F() - ค่าเริ่มต้นFซึ่งเริ่มต้นโดยค่าเริ่มต้น Fตั้งแต่ 1. จุดใน[dcl.init] / 8 ( Fฟังก์ชัน ctor จะให้โดยผู้ใช้หากมีการประกาศโดยผู้ใช้และไม่ได้กำหนดค่าเริ่มต้นหรือลบอย่างชัดเจนในการประกาศครั้งแรกลิงก์ )


มีคำอธิบายที่ดีที่นี่: en.cppreference.com/w/cpp/language/default_constructor
Richard Hodges

1
เท่าที่ฉันสามารถบอกได้มีเพียงความแตกต่างระหว่าง C ++ 98 และ C ++ 03 ในตัวอย่างเหล่านี้ ปัญหาที่ดูเหมือนว่าจะอธิบายไว้ในN1161 (มีการแก้ไขในภายหลังของเอกสารนั้น) และCWG DR # ถ้อยคำที่จำเป็นในการเปลี่ยนแปลงใน C ++ 11 เนื่องจากคุณสมบัติใหม่และข้อกำหนดใหม่ของ POD และการเปลี่ยนแปลงอีกครั้งใน C ++ 14 เนื่องจากความบกพร่องใน C ++ 11 ถ้อยคำ แต่ผลกระทบในกรณีเหล่านี้จะไม่เปลี่ยนแปลง .
dyp

3
ในขณะที่น่าเบื่อstruct D { D() {}; int m; };อาจมีมูลค่ารวมอยู่ในรายการของคุณ
Yakk - Adam Nevraumont

คำตอบ:


24

C ++ 14 ระบุการเริ่มต้นของอ็อบเจ็กต์ที่สร้างด้วยnewใน [expr.new] / 17 ([expr.new] / 15 ใน C ++ 11 และโน้ตไม่ใช่โน้ต แต่เป็นข้อความเชิงบรรทัดฐานในตอนนั้น):

ใหม่แสดงออกที่สร้างวัตถุของการพิมพ์ที่Tเริ่มต้นว่าวัตถุดังต่อไปนี้:

  • หากไม่ระบุnew-initializerอ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้น (8.5) [ หมายเหตุ:หากไม่มีการกำหนดค่าเริ่มต้นอ็อบเจ็กต์จะมีค่าที่ไม่แน่นอน - หมายเหตุ ]
  • มิฉะนั้นโปรแกรมเริ่มต้นใหม่จะถูกตีความตามกฎการเตรียมใช้งานที่ 8.5 สำหรับการเริ่มต้นโดยตรง

การเริ่มต้นเริ่มต้นถูกกำหนดไว้ใน [dcl.init] / 7 (/ 6 ใน C ++ 11 และคำพูดเองก็มีผลเช่นเดียวกัน):

ในการกำหนดค่าเริ่มต้นวัตถุประเภทTหมายถึง:

  • ถ้าTเป็นประเภทคลาส (อาจมีคุณสมบัติ cv) (ข้อ 9) ตัวสร้างเริ่มต้น (12.1) สำหรับTถูกเรียก (และการเตรียมใช้งานจะมีรูปแบบไม่ถูกต้องหากTไม่มีตัวสร้างเริ่มต้นหรือความละเอียดเกินพิกัด (13.3) ส่งผลให้เกิดความไม่ชัดเจนหรือใน ฟังก์ชันที่ถูกลบหรือไม่สามารถเข้าถึงได้จากบริบทของการเริ่มต้น);
  • ถ้าTเป็นประเภทอาร์เรย์แต่ละองค์ประกอบคือเริ่มต้นเริ่มต้น ;
  • มิฉะนั้นจะไม่มีการเริ่มต้น

ด้วยประการฉะนี้

  • new Aแต่เพียงผู้เดียวสาเหตุAs mสร้างเริ่มต้นที่จะเรียกว่าซึ่งจะไม่เริ่มต้น ค่าไม่แน่นอน ควรจะเหมือนกันสำหรับnew B.
  • new A() ถูกตีความตาม [dcl.init] / 11 (/ 10 ใน C ++ 11):

    ออบเจ็กต์ที่มีค่าเริ่มต้นเป็นชุดวงเล็บว่างกล่าวคือ()จะต้องกำหนดค่าเริ่มต้น

    ตอนนี้พิจารณา [dcl.init] / 8 (/ 7 ใน C ++ 11 †):

    ในการกำหนดค่าเริ่มต้นวัตถุประเภทTหมายถึง:

    • ถ้าTเป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ที่ไม่มีตัวสร้างเริ่มต้น (12.1) หรือตัวสร้างเริ่มต้นที่ผู้ใช้ระบุหรือถูกลบออกจากนั้นอ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้น
    • ถ้าTเป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) โดยไม่มีคอนสตรัคเตอร์ดีฟอลต์ที่ผู้ใช้ระบุหรือถูกลบอ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้นเป็นศูนย์และจะมีการตรวจสอบข้อ จำกัด ทางความหมายสำหรับการเริ่มต้นดีฟอลต์และหาก T มีคอนสตรัคเตอร์เริ่มต้นที่ไม่สำคัญ วัตถุถูกกำหนดค่าเริ่มต้น
    • ถ้าTเป็นประเภทอาร์เรย์แต่ละองค์ประกอบจะถูกกำหนดค่าเริ่มต้น
    • มิฉะนั้นวัตถุจะเริ่มต้นเป็นศูนย์

    ดังนั้นจะเป็นศูนย์การเริ่มต้นnew A() mและนี้ควรจะเทียบเท่าและAB

  • new Cและnew C()จะเริ่มต้นอ็อบเจ็กต์โดยปริยายอีกครั้งเนื่องจากสัญลักษณ์แสดงหัวข้อย่อยแรกจากเครื่องหมายคำพูดสุดท้ายใช้ (C มีตัวสร้างเริ่มต้นที่ผู้ใช้ระบุ!) แต่เห็นได้ชัดว่าตอนนี้mเริ่มต้นในตัวสร้างในทั้งสองกรณี


†ย่อหน้านี้มีถ้อยคำที่แตกต่างกันเล็กน้อยใน C ++ 11 ซึ่งไม่ได้เปลี่ยนแปลงผลลัพธ์:

ในการกำหนดค่าเริ่มต้นวัตถุประเภทTหมายถึง:

  • ถ้าTเป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ที่มีคอนสตรัคเตอร์ที่ผู้ใช้ระบุ (12.1) ดังนั้นคอนสตรัคเตอร์เริ่มต้นสำหรับT จะถูกเรียก (และการกำหนดค่าเริ่มต้นจะมีรูปแบบไม่ถูกต้องหาก T ไม่มีตัวสร้างเริ่มต้นที่สามารถเข้าถึงได้)
  • ถ้าTเป็นประเภทคลาสที่ไม่เป็นสหภาพ (อาจเป็น cv ที่มีคุณสมบัติเหมาะสม) โดยไม่มีตัวสร้างที่ผู้ใช้กำหนดอ็อบเจ็กต์นั้นจะถูกกำหนดค่าเริ่มต้นเป็นศูนย์และถ้าTตัวสร้างเริ่มต้นที่ประกาศโดยนัยไม่สำคัญตัวสร้างนั้นจะถูกเรียกว่า
  • ถ้าTเป็นประเภทอาร์เรย์แต่ละองค์ประกอบจะถูกกำหนดค่าเริ่มต้น
  • มิฉะนั้นวัตถุจะเริ่มต้นเป็นศูนย์

อ่าคุณกำลังพูดถึง c ++ 14 เป็นหลักและการอ้างอิงสำหรับ c ++ 11 จะอยู่ในวงเล็บ
Gabriel

@ กาเบรียลถูกต้อง. ฉันหมายความว่า C ++ 14 เป็นมาตรฐานล่าสุดดังนั้นนั่นคือสิ่งที่สำคัญที่สุด
Columbo

1
สิ่งที่น่ารำคาญเกี่ยวกับการพยายามติดตามกฎการเริ่มต้นข้ามมาตรฐานคือการเปลี่ยนแปลงจำนวนมาก (ส่วนใหญ่ทั้งหมด?) ระหว่างมาตรฐาน C ++ 14 และ C ++ 11 ที่เผยแพร่เกิดขึ้นผ่าน DRs และโดยพฤตินัย C ++ 11 . แล้วยังมี post-C ++ 14 DR ด้วย ...
TC

@ Columbo ฉันยังไม่เข้าใจว่าเหตุใดจึงstruct A { int m; }; struct C { C() : m(){}; int m; };ให้ผลลัพธ์ที่แตกต่างกันและอะไรทำให้ m ใน A ถูกเริ่มต้นตั้งแต่แรก ฉันได้เปิดหัวข้อเฉพาะสำหรับการทดลองที่ฉันทำและฉันจะขอบคุณข้อมูลของคุณที่นั่นเพื่อชี้แจงปัญหา ขอบคุณstackoverflow.com/questions/45290121/…
darkThoughts

12

คำตอบต่อไปนี้ขยายคำตอบhttps://stackoverflow.com/a/620402/977038ซึ่งจะใช้เป็นข้อมูลอ้างอิงสำหรับ C ++ 98 และ C ++ 03

อ้างคำตอบ

  1. ใน C ++ 1998 มีการเริ่มต้น 2 ประเภท: ศูนย์และค่าเริ่มต้น
  2. ใน C ++ 2003 เป็นการเริ่มต้นประเภทที่ 3 การเริ่มต้นค่าถูกเพิ่ม

C ++ 11 (อ้างอิงถึง n3242)

ตัวเริ่มต้น

8.5 Initializers [dcl.init] ระบุว่าตัวแปร POD หรือ non POD สามารถเริ่มต้นได้ไม่ว่าจะเป็นbrace-or-equal-initializerซึ่งอาจเป็น braced-init-listหรือinitializer-clauseรวมเรียกว่า brace-or-equal- การเริ่มต้นหรือการใช้(การแสดงออกรายการ) ก่อนหน้า C ++ 11 รองรับเฉพาะ(expression-list)หรือinitializer-clauseแม้ว่าinitializer-clauseจะถูก จำกัด มากกว่าสิ่งที่เรามีใน C ++ 11 ใน C ++ 11 ตอนนี้initializer-clauseรองรับbraced-init-listนอกเหนือจากการกำหนดนิพจน์เช่นเดียวกับใน C ++ 03 ไวยากรณ์ต่อไปนี้สรุปอนุประโยคใหม่ที่รองรับโดยที่ส่วนที่เป็นตัวหนาจะถูกเพิ่มเข้ามาใหม่ในมาตรฐาน C ++ 11

initializer:
    brace-or-equal-initializer
    (expression-list)
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ... opt
    initializer-list, initializer-clause ... opt **
braced-init-list:
    {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 ที่มีคุณสมบัติเป็น cv)สมาชิกข้อมูลแต่ละตัวที่ไม่คงที่และแต่ละวัตถุย่อยระดับฐานจะเริ่มต้นเป็นศูนย์และช่องว่างภายในจะเริ่มต้นเป็นศูนย์บิต
  • ถ้า T เป็นชนิดยูเนี่ยน(อาจมีคุณสมบัติเป็น cv)สมาชิกข้อมูลที่มีชื่อแบบไม่คงที่ตัวแรกของออบเจ็กต์จะเริ่มต้นเป็นศูนย์และช่องว่างภายในจะเริ่มต้นเป็นศูนย์บิต
  • ถ้า T เป็นประเภทอาร์เรย์แต่ละองค์ประกอบจะเริ่มต้นเป็นศูนย์
  • ถ้า T เป็นชนิดอ้างอิงจะไม่มีการเริ่มต้น

2. ประเภทตัวเริ่มต้น: 8.5.6 [dcl.init] _default-initialize_

ดำเนินการในกรณีต่อไปนี้

  • หากไม่ระบุ new-initializer อ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้นโดยปริยาย หากไม่มีการเตรียมใช้งานวัตถุจะมีค่าไม่แน่นอน
  • หากไม่ได้ระบุ initializer สำหรับอ็อบเจ็กต์อ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้นเป็นค่าดีฟอลต์ยกเว้นอ็อบเจ็กต์ที่มีระยะเวลาการจัดเก็บแบบคงที่หรือเธรด
  • เมื่อไม่มีการกล่าวถึงคลาสฐานหรือสมาชิกข้อมูลที่ไม่คงที่ในรายการตัวสร้างเริ่มต้นและตัวสร้างนั้นถูกเรียกใช้

ในการเริ่มต้นเริ่มต้นวัตถุประเภท T หมายถึง:

  • ถ้า T เป็น(อาจจะเป็นพันธุ์ที่มีคุณสมบัติ) รุนไม่ใช่ประเภทชั้น (ข้อ 9) ผู้สร้างเริ่มต้นของ T เรียกว่า (และเริ่มต้นจะไม่ดีที่เกิดขึ้นถ้า T ยังไม่มีการสร้างเริ่มต้นที่สามารถเข้าถึงได้);
  • ถ้า T เป็นประเภทอาร์เรย์แต่ละองค์ประกอบจะถูกกำหนดค่าเริ่มต้น
  • มิฉะนั้นจะไม่มีการเริ่มต้น

หมายเหตุจนถึง C ++ 11 เฉพาะคลาสที่ไม่ใช่ POD ที่มีระยะเวลาการจัดเก็บอัตโนมัติเท่านั้นที่จะถือว่าเป็นค่าเริ่มต้นเริ่มต้นเมื่อไม่มีการใช้ตัวเริ่มต้น


3. ประเภท Initializer: 8.5.7 [dcl.init] _value-initialize_

  1. เมื่อออบเจ็กต์ (ไม่มีชื่อชั่วคราวตัวแปรที่มีชื่อระยะเวลาการจัดเก็บแบบไดนามิกหรือสมาชิกข้อมูลที่ไม่คงที่) ซึ่งตัวเริ่มต้นเป็นชุดวงเล็บว่างเปล่าเช่น () หรือวงเล็บปีกกา {}

ในการกำหนดค่าเริ่มต้นวัตถุประเภท T หมายถึง:

  • ถ้า T เป็นประเภทคลาส(อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ที่มีตัวสร้างที่ผู้ใช้ระบุ (12.1) ตัวสร้างเริ่มต้นสำหรับ T จะถูกเรียก (และการกำหนดค่าเริ่มต้นจะมีรูปแบบไม่ถูกต้องหาก T ไม่มีตัวสร้างเริ่มต้นที่สามารถเข้าถึงได้) ;
  • ถ้า T เป็นประเภทคลาสที่ไม่มีการรวมกัน (อาจเป็น cv ที่มีคุณสมบัติเป็น cv) โดยไม่มีตัวสร้างที่ผู้ใช้จัดหาให้สมาชิกข้อมูลที่ไม่คงที่และส่วนประกอบพื้นฐานของ T ทุกตัวจะถูกกำหนดค่า จากนั้นอ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้นเป็นศูนย์และหากตัวสร้างเริ่มต้นที่ประกาศโดยนัยของ T นั้นไม่สำคัญตัวสร้างนั้นจะถูกเรียก
  • ถ้า T เป็นประเภทอาร์เรย์ดังนั้นแต่ละองค์ประกอบจะถูกกำหนดค่าเริ่มต้น
  • มิฉะนั้นวัตถุจะเริ่มต้นเป็นศูนย์

เพื่อสรุป

หมายเหตุใบเสนอราคาที่เกี่ยวข้องจากมาตรฐานจะเน้นเป็นตัวหนา

  • new A: default-initializes (ปล่อยให้ A :: m uninitialized)
  • new A (): เริ่มต้น A เป็นศูนย์เนื่องจากค่าเริ่มต้นผู้สมัครไม่มีตัวสร้างเริ่มต้นที่ผู้ใช้ระบุหรือถูกลบ ถ้า T เป็นประเภทคลาสที่ไม่มีการรวมกัน (อาจเป็น cv ที่มีคุณสมบัติเป็น cv) โดยไม่มีคอนสตรัคเตอร์ที่ผู้ใช้กำหนดอ็อบเจ็กต์นั้นจะถูกกำหนดค่าเริ่มต้นเป็นศูนย์และถ้าตัวสร้างเริ่มต้นที่ประกาศโดยนัยของ T นั้นไม่สำคัญตัวสร้างนั้นจะถูกเรียกว่า
  • new B: default-initializes (จาก B :: m uninitialized)
  • new B (): ค่าเริ่มต้น B ซึ่งเป็นศูนย์เริ่มต้นฟิลด์ทั้งหมด ถ้า T เป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ที่มีตัวสร้างที่ผู้ใช้ระบุ (12.1) ตัวสร้างเริ่มต้นสำหรับ T จะถูกเรียกว่า
  • new C: default-initializes C ซึ่งเรียก ctor เริ่มต้น ถ้า T เป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ตัวสร้างเริ่มต้นสำหรับ T จะถูกเรียกว่ายิ่งไปกว่านั้นถ้าไม่ใส่ตัวเริ่มต้นใหม่อ็อบเจ็กต์จะถูกกำหนดค่าเริ่มต้น
  • new C (): ค่าเริ่มต้น C ซึ่งเรียก ctor เริ่มต้น ถ้า T เป็นประเภทคลาส (อาจมีคุณสมบัติเป็น cv) (ข้อ 9) ที่มีคอนสตรัคเตอร์ที่ผู้ใช้ระบุ (12.1) ตัวสร้างเริ่มต้นสำหรับ T จะถูกเรียก ยิ่งไปกว่านั้นอ็อบเจ็กต์ที่มี initializer เป็นชุดวงเล็บว่างกล่าวคือ () จะถูกกำหนดค่าเริ่มต้น

0

ฉันสามารถยืนยันได้ว่าใน C ++ 11 ทุกสิ่งที่กล่าวถึงในคำถามภายใต้ C ++ 14 นั้นถูกต้องอย่างน้อยตามการใช้งานคอมไพเลอร์

เพื่อตรวจสอบนี้ผมเพิ่มรหัสต่อไปนี้ฉันชุดทดสอบ ฉันทดสอบด้วย-std=c++11 -O3ใน GCC 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.mอาจเท่ากับ 42, 0 หรือขยะอื่น ๆ ) สถานที่ที่~UBกล่าวถึงเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในทางทฤษฎี แต่ในทางปฏิบัติเนื่องจากการใช้ตำแหน่งใหม่จึงไม่น่าa->mจะเท่ากับสิ่งอื่นใดที่มากกว่า 42

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.