ความจำเป็นของการกำหนดค่าเริ่มต้นโดยไม่มีคอนสตรักเตอร์
(consteval.ca)ฉันไม่มีคอนสตรักเตอร์และต้องกำหนดค่าเริ่มต้น
-
บทนำ
- ตอนเริ่มเรียน C++ ครั้งแรก ได้เรียนมาว่าคอมไพเลอร์จะจัดเตรียม default constructor ให้ในบางกรณี
- จึงเริ่มคิดถึงความเสี่ยงที่อ็อบเจ็กต์อาจไม่ได้รับการกำหนดค่าเริ่มต้นในบางสถานการณ์
-
default initialization และ value initialization
T t;จะทำ default initialization- ถ้า
Tเป็นชนิดคลาสและมี default constructor ก็จะเรียกใช้งาน - ถ้า
Tเป็นชนิดอาร์เรย์ ก็จะทำ default initialization ให้แต่ละองค์ประกอบ - นอกเหนือจากนั้นจะไม่ทำอะไรเลย
- ถ้า
T t{};จะทำ value initialization- ถ้า
Tเป็นชนิดคลาส และไม่มี default constructor หรือมี default constructor ที่ผู้ใช้จัดเตรียมหรือถูกลบ ก็จะทำ default initialization - มิฉะนั้นจะกำหนดค่าเป็น 0 ก่อน แล้วจึงทำ default initialization
- ถ้า
Tเป็นชนิดอาร์เรย์ ก็จะทำ value initialization ให้แต่ละองค์ประกอบ - นอกเหนือจากนั้นจะกำหนดค่าเป็น 0
- ถ้า
-
default constructor
- ถ้าไม่ได้ประกาศ default constructor ไว้ คอมไพเลอร์จะประกาศ default constructor แบบปริยายให้
- default constructor ที่ประกาศแบบปริยายจะมีเนื้อความว่างและรายการกำหนดค่าเริ่มต้นของเมมเบอร์ว่าง
- ตัวอย่าง:
struct T { int x; T() = default; }; T t{}; std::cout << t.x << std::endl; // ผลลัพธ์คือ 0
-
default constructor ที่นิยามแบบปริยาย
- ถ้า default constructor ถูกประกาศแบบปริยาย หรือประกาศเป็นค่าเริ่มต้นอย่างชัดเจน คอมไพเลอร์จะจัดเตรียม default constructor ที่นิยามแบบปริยายให้
- ตัวอย่าง:
struct T { T(); }; T::T() = default; T t{}; std::cout << t.x << std::endl; // ผลลัพธ์เป็นค่าขยะ
-
กรณีที่ไม่สามารถจัดเตรียม default constructor ได้
- เมื่อ
Tมีสมาชิกแบบอ้างอิงที่ไม่เป็น static - เมื่อ
Tมีสมาชิกที่ไม่เป็น static หรือคลาสฐานที่ไม่เป็น abstract ซึ่งไม่สามารถ default construct หรือ destruct ได้ - เมื่อ
Tมีสมาชิกconstที่ไม่เป็น static และไม่มี default member initializer
- เมื่อ
-
การกำหนดค่าเริ่มต้นที่ถูกต้อง
T t{};จะทำ list initialization- list initialization แบ่งเป็น direct-list initialization และ copy-list initialization
- ตัวอย่าง:
struct S { int a; float b; char c; }; S s{3, 4.0f, 'S'}; // ไม่มีการเรียกคอนสตรักเตอร์
-
list initialization และ aggregate initialization
- aggregate initialization เป็นรูปแบบพิเศษของ list initialization โดยจะทำ copy initialization ให้แต่ละองค์ประกอบของคลาสหรืออาร์เรย์ด้วยแต่ละค่าจากรายการกำหนดค่าเริ่มต้น
- ตัวอย่าง:
struct A { const int x; }; A a{}; // a.x ถูกกำหนดค่าเริ่มต้นเป็น 0
-
การกำหนดค่าเริ่มต้นด้วยวงเล็บ
- การกำหนดค่าเริ่มต้นด้วยวงเล็บจะทำ direct non-list initialization
- ตัวอย่าง:
struct T { const int& r; }; T t(42); // t.r เป็นการอ้างอิงไปยัง 42
-
สรุป
- กฎของการกำหนดค่าเริ่มต้นมีความซับซ้อน แต่การเขียนคอนสตรักเตอร์เองจะช่วยหลีกเลี่ยงปัญหาส่วนใหญ่ได้
- ไม่ควรปล่อยให้คอมไพเลอร์จัดการทั้งหมด แต่ควรเขียนคอนสตรักเตอร์ด้วยตนเอง
ความเห็นของ GN⁺
- บทความนี้อธิบายความซับซ้อนของกฎการกำหนดค่าเริ่มต้นใน C++ ได้อย่างดี
- การเข้าใจกฎการกำหนดค่าเริ่มต้นของ C++ เป็นเรื่องสำคัญ เพราะส่งผลอย่างมากต่อความเสถียรและประสิทธิภาพของโค้ด
- การเขียนคอนสตรักเตอร์เองเป็นวิธีที่ดีที่สุดในการหลีกเลี่ยงปัญหาเกี่ยวกับการกำหนดค่าเริ่มต้น
- ภาษาอื่นที่มีความสามารถคล้ายกันคือ Rust ซึ่งมีกฎการกำหนดค่าเริ่มต้นที่ชัดเจนกว่า
- เมื่อนำเทคโนโลยีใหม่มาใช้ การเข้าใจและใช้งานรายละเอียดอย่างกฎการกำหนดค่าเริ่มต้นให้ดีเป็นสิ่งสำคัญ
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ผลลัพธ์การกำหนดค่าเริ่มต้นของ
tจะเป็น 0tถูกกำหนดค่าเริ่มต้นแบบค่า และเนื่องจากTไม่มีตัวสร้างเริ่มต้นแบบกำหนดเอง ออบเจ็กต์จึงถูกทำให้เป็น 0 ก่อนแล้วจึงค่อยเรียกตัวสร้างเริ่มต้นตัวสร้างเริ่มต้นจะกำหนดค่าเริ่มต้นให้สมาชิกแบบค่าเริ่มต้น ซึ่งต่างจากการกำหนดค่าเริ่มต้นแบบค่า
ดูเหมือนว่า GCC จะเห็นด้วยกับเรื่องนี้
ผู้เขียนมองข้ามไปว่าจริง ๆ แล้วกำลังกำหนดค่าเริ่มต้นให้
xแบบค่ารายละเอียดของกฎมีความซับซ้อนและบางครั้งก็มีส่วนที่ไม่สมเหตุสมผล
การทำให้การกำหนดค่าเริ่มต้นแบบดีฟอลต์เป็นสิ่งที่ระบุได้อย่างชัดเจนจะเป็นการปรับปรุงครั้งใหญ่
std::array<int, 100> = void;น่าจะดีกว่าจุดเชื่อมระหว่างการกำหนดค่าเริ่มต้นแบบลิสต์กับการกำหนดค่าเริ่มต้นแบบ aggregate คือ เมื่อทำการกำหนดค่าเริ่มต้นแบบลิสต์กับ aggregate ก็จะเกิดการกำหนดค่าเริ่มต้นแบบ aggregate
กรณีที่มีหนึ่งองค์ประกอบทำงานไม่เหมือนกรณีที่มีตั้งแต่สององค์ประกอบขึ้นไป
สามารถเขียนตัวสร้างของตัวเองได้ และกำหนดค่าเริ่มต้นให้ทูเพิลหรืออาร์เรย์ที่มีเพียงหนึ่งองค์ประกอบได้
ตอนที่ C++11 initializer list ออกมาใหม่ ๆ เคยเจอเรื่องนี้และคิดว่ามันบ้าสุด ๆ
มีการกล่าวถึง "I Have No Mouth, and I Must Scream" (1967)
ใช้ไวยากรณ์
T::T() = default;คาดว่าผลลัพธ์ที่พิมพ์ออกมาจะเป็น 0 แต่จริง ๆ แล้วจะได้ค่า garbage
เปิดให้ผู้ใช้ไลบรารีสามารถเปลี่ยนพฤติกรรมของไลบรารีได้
ถ้าอยากได้ความซับซ้อนของ C++ เพิ่มอีก แนะนำ C++ FQA
ธีมของบล็อกได้แรงบันดาลใจจากคอมพิวเตอร์ยุค DEC แต่ดูสะอาดตาและมินิมอล
อ่านเรื่องนี้แล้วรู้สึกมึนหัว
Go และ Rust ไม่มีตัวสร้างแบบพิเศษ จึงทำให้หลายอย่างง่ายขึ้นมาก
สงสัยว่ามีเครื่องมือ C++ ที่แสดงพฤติกรรมโดยนัยทั้งหมดหรือไม่
มีการให้ข้อมูลผิดเกี่ยวกับคลาส
คำกล่าวที่ว่า "
T t;ไม่ได้ทำอะไรเลย" นั้นไม่ถูกต้องT t;จะล้มเหลวส่วนหัวของบล็อกมีแผงหน้าของ DEC