ซี++11
| การปรับปรุงภาษา C++ |
|---|
C++11เป็นเวอร์ชันของมาตรฐานทางเทคนิค ร่วม ISO/IEC 14882 โดยองค์การมาตรฐานสากล (ISO) และคณะกรรมการไฟฟ้าสากล (IEC) สำหรับ ภาษาการเขียนโปรแกรม C++ C++11 แทนที่มาตรฐาน C++ เวอร์ชันก่อนหน้าที่มีชื่อว่าC++03 [ 1 ]และต่อมาถูกแทนที่ด้วยC++14ชื่อนี้เป็นไปตามธรรมเนียมการตั้งชื่อเวอร์ชันภาษาตามปีที่เผยแพร่ข้อกำหนด แม้ว่าก่อนหน้านี้จะมีชื่อว่าC ++0xเนื่องจากคาดว่าจะเผยแพร่ก่อนปี 2010 [ 2 ]
แม้ว่าเป้าหมายการออกแบบประการหนึ่งคือการให้ความสำคัญกับการเปลี่ยนแปลงไลบรารีมากกว่าการเปลี่ยนแปลงภาษาหลัก[ 3 ] C++11 ก็ได้เพิ่มส่วนต่างๆ ของภาษาหลักเข้ามาหลายส่วน ส่วนต่างๆ ของภาษาหลักที่ได้รับการปรับปรุงอย่างมีนัยสำคัญ ได้แก่ การสนับสนุนมัลติเธรด การสนับสนุน การเขียนโปรแกรมแบบเจเนริกการเริ่มต้นแบบสม่ำเสมอ และประสิทธิภาพ นอกจากนี้ยังมีการเปลี่ยนแปลงที่สำคัญในไลบรารีมาตรฐานของ C++ โดยรวม ไลบรารีส่วนใหญ่ของรายงานทางเทคนิค C++ ฉบับที่ 1 (TR1) ยกเว้นไลบรารีของฟังก์ชันพิเศษทางคณิตศาสตร์[ 4 ]
C++11 ได้รับการเผยแพร่เป็นISO/IEC 14882:2011 [ 5 ]ในเดือนกันยายน พ.ศ. 2554 และมีให้บริการโดยเสียค่าธรรมเนียม ร่างงานที่คล้ายคลึงกับมาตรฐาน C++11 ที่เผยแพร่มากที่สุดคือ N3337 ลงวันที่ 16 มกราคม พ.ศ. 2555 [ 6 ]ซึ่งมีการแก้ไขด้านบรรณาธิการจากมาตรฐาน C++11 เท่านั้น[ 7 ]
C++11 ได้รับการสนับสนุนอย่างเต็มที่โดยClang 3.3 และเวอร์ชันต่อมา[ 8 ]และโดยGNU Compiler Collection (GCC) 4.8.1 และเวอร์ชันต่อมา[ 9 ]
เป้าหมายการออกแบบ
คณะกรรมการออกแบบพยายามยึดมั่นในเป้าหมายหลายประการในการออกแบบ C++11:
- รักษาเสถียรภาพและความเข้ากันได้กับโค้ดเวอร์ชันเก่า
- ควรเลือกวิธีการเพิ่มฟังก์ชันใหม่ผ่านไลบรารีมาตรฐาน มากกว่าการขยายภาษาหลัก
- ปรับปรุง C++ เพื่ออำนวยความสะดวกในการออกแบบระบบและไลบรารี แทนที่จะเพิ่มคุณสมบัติใหม่ที่ใช้ได้ผลเฉพาะกับแอปพลิเคชันเฉพาะกลุ่มเท่านั้น
- เพิ่มความปลอดภัยในการพิมพ์โดยนำเสนอทางเลือกที่ปลอดภัยกว่าแทนเทคนิคที่ไม่ปลอดภัยในอดีต
- เพิ่มประสิทธิภาพและความสามารถในการทำงานร่วมกับฮาร์ดแวร์โดยตรง
- นำเสนอแนวทางแก้ไขที่เหมาะสมสำหรับปัญหาในโลกแห่งความเป็นจริง
- ทำให้การสอนและการเรียนรู้ C++ เป็นเรื่องง่าย โดยไม่ลดทอนประโยชน์ใช้สอยใดๆ ที่จำเป็นสำหรับโปรแกรมเมอร์ผู้เชี่ยวชาญ
การให้ความสนใจกับผู้เริ่มต้นถือเป็นสิ่งสำคัญ เนื่องจากโปรแกรมเมอร์คอมพิวเตอร์ส่วนใหญ่จะเป็นผู้เริ่มต้นเสมอ และเนื่องจากผู้เริ่มต้นหลายคนไม่เคยขยายความรู้ของตนเอง โดยจำกัดตัวเองให้ทำงานเฉพาะในด้านภาษาที่ตนเองเชี่ยวชาญ[ 2 ] [ 3 ]
ส่วนขยายของภาษาหลัก C++
หน้าที่หนึ่งของคณะกรรมการ C++ คือการพัฒนาส่วนหลักของภาษา ส่วนต่างๆ ของภาษาหลักที่ได้รับการปรับปรุงอย่างมีนัยสำคัญ ได้แก่การรองรับมัลติเธรด ดิ้ง การรองรับ การเขียนโปรแกรมแบบเจเนริกการเริ่มต้นค่าแบบสม่ำเสมอ และประสิทธิภาพการทำงาน
การปรับปรุงประสิทธิภาพการทำงานของรันไทม์ภาษาหลัก
คุณลักษณะทางภาษาเหล่านี้มีอยู่เป็นหลักเพื่อเพิ่ม ประสิทธิภาพการ ทำงานในขณะรันไทม์ไม่ว่าจะเป็นด้านหน่วยความจำหรือความเร็วในการประมวลผล
การอ้างอิง Rvalue และตัวสร้างการย้าย
ใน C++03 (และก่อนหน้านั้น) ค่าชั่วคราว (เรียกว่า " rvalues " เนื่องจากมักจะอยู่ทางด้านขวาของการกำหนดค่า) มีจุดประสงค์เพื่อไม่ให้สามารถแก้ไขได้ — เช่นเดียวกับใน C — และถือว่าไม่แตกต่างจากconst T&ประเภท อย่างไรก็ตาม ในบางกรณี ค่าชั่วคราวสามารถแก้ไขได้ ซึ่งพฤติกรรมนี้ถือว่าเป็นช่องโหว่ที่มีประโยชน์ด้วยซ้ำ[ 10 ] C++11 เพิ่ม ประเภทอ้างอิงที่ไม่ใช่ const ใหม่ที่เรียกว่าการอ้างอิงค่า rvalueที่ระบุโดยT&&. สิ่งนี้หมายถึงค่าชั่วคราวที่อนุญาตให้แก้ไขได้หลังจากเริ่มต้นใช้งานแล้ว เพื่อวัตถุประสงค์ในการอนุญาตให้ใช้ "ความหมายของการย้าย" (move semantics)
ปัญหาประสิทธิภาพเรื้อรังอย่างหนึ่งของ C++03 คือ การคัดลอกข้อมูลแบบลึกที่ไม่จำเป็นและมีค่าใช้จ่ายสูงซึ่งอาจเกิดขึ้นโดยปริยายเมื่อมีการส่งผ่านอ็อบเจ็กต์ด้วยค่า เพื่ออธิบายปัญหานี้ ลองพิจารณาว่าตัวแปร `array` นั้นstd::vector<T>ภายในแล้วเป็นตัวห่อหุ้มอาร์เรย์แบบ C ที่มีขนาดที่กำหนดไว้ หากstd::vector<T>มีการสร้างหรือส่งคืนตัวแปรชั่วคราวจากฟังก์ชัน จะสามารถจัดเก็บได้โดยการสร้างตัวแปรใหม่std::vector<T>และคัดลอกข้อมูลทั้งหมดของค่าที่ส่งคืนเข้าไปเท่านั้น จากนั้นตัวแปรชั่วคราวและหน่วยความจำทั้งหมดจะถูกทำลาย (เพื่อความง่าย ในการอธิบายนี้จะละเว้นการเพิ่มประสิทธิภาพของค่าที่ส่งคืน )
ใน C++11 นั้นคอนสตรัคเตอร์แบบย้าย (move constructor ) ที่std::vector<T>รับการอ้างอิง rvalue ไปยังออบเจ็กต์ชั่วคราวstd::vector<T>สามารถคัดลอกตัวชี้ไปยังอาร์เรย์แบบ C ภายในจาก rvalue ไปstd::vector<T>ยังออบเจ็กต์ชั่วคราวใหม่ จากนั้นตั้งค่าตัวชี้ภายใน rvalue เป็น null เนื่องจากออบเจ็กต์ชั่วคราวจะไม่ถูกใช้งานอีกต่อไป จึงไม่มีโค้ดใดพยายามเข้าถึงตัวชี้ที่เป็น null และเนื่องจากตัวชี้เป็น null หน่วยความจำของมันจึงไม่ถูกลบเมื่อมันอยู่นอกขอบเขต ดังนั้น การดำเนินการนี้จึงไม่เพียงแต่ประหยัดค่าใช้จ่ายในการคัดลอกแบบลึก แต่ยังปลอดภัยและมองไม่เห็นอีกด้วย
การอ้างอิงแบบ Rvalue สามารถเพิ่มประสิทธิภาพให้กับโค้ดที่มีอยู่โดยไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ นอกเหนือจากไลบรารีมาตรฐาน ชนิดของค่าที่ส่งคืนจากฟังก์ชันที่ส่งคืนค่าstd::vector<T>ชั่วคราวไม่จำเป็นต้องเปลี่ยนแปลงอย่างชัดเจนเพื่อstd::vector<T>&&เรียกใช้คอนสตรัคเตอร์แบบย้าย เนื่องจากค่าชั่วคราวจะถูกพิจารณาว่าเป็น Rvalue โดยอัตโนมัติ (อย่างไรก็ตาม หากstd::vector<T>เป็นเวอร์ชัน C++03 ที่ไม่มีคอนสตรัคเตอร์แบบย้าย คอนสตรัคเตอร์แบบคัดลอกจะถูกเรียกใช้ด้วยconst std::vector<T>ค่า Rvalue ซึ่งจะทำให้เกิดการจัดสรรหน่วยความจำจำนวนมาก)
เพื่อความปลอดภัย จึงมีการกำหนดข้อจำกัดบางประการ ตัวแปรที่มีชื่อจะไม่ถือว่าเป็น rvalue แม้ว่าจะประกาศไว้เช่นนั้นก็ตาม หากต้องการได้ rvalue std::move()จะต้องใช้เทมเพลตฟังก์ชัน นอกจากนี้ การอ้างอิง rvalue สามารถแก้ไขได้ภายใต้สถานการณ์บางอย่างเท่านั้น โดยมีจุดประสงค์หลักเพื่อใช้กับคอนสตรัคเตอร์แบบย้าย (move constructor)
เนื่องจากลักษณะเฉพาะของการใช้คำใน rvalue references และการปรับเปลี่ยนคำบางส่วนสำหรับ lvalue references (regular references) ทำให้ rvalue references ช่วยให้นักพัฒนาสามารถส่งต่อฟังก์ชันได้อย่างสมบูรณ์แบบ เมื่อรวมกับvariadic templatesความสามารถนี้จะช่วยให้สามารถสร้าง function templates ที่สามารถส่งต่ออาร์กิวเมนต์ไปยังฟังก์ชันอื่นที่รับอาร์กิวเมนต์เหล่านั้นได้อย่างสมบูรณ์แบบ ซึ่งมีประโยชน์อย่างยิ่งสำหรับการส่งต่อพารามิเตอร์ของ constructor เพื่อสร้าง factory function ที่จะเรียก constructor ที่ถูกต้องสำหรับอาร์กิวเมนต์เหล่านั้นโดยอัตโนมัติ ตัวอย่างเช่น ใน ชุด เมธอด emplace_backของไลบรารีมาตรฐาน C++
constexpr – นิพจน์ค่าคงที่ทั่วไป
ภาษา C++ มีแนวคิดเรื่องนิพจน์คงที่มาโดยตลอด นิพจน์เหล่านี้ เช่น `constant expression` 3+4จะให้ผลลัพธ์เหมือนกันเสมอ ทั้งในระหว่างการคอมไพล์และการทำงาน นิพจน์คงที่เป็นโอกาสในการเพิ่มประสิทธิภาพสำหรับคอมไพเลอร์ และคอมไพเลอร์มักจะประมวลผลนิพจน์เหล่านี้ในระหว่างการคอมไพล์และกำหนดผลลัพธ์ลงในโปรแกรมโดยตรง นอกจากนี้ ในหลายๆ ที่ ข้อกำหนดของ C++ ยังกำหนดให้ใช้นิพจน์คงที่ การกำหนดอาร์เรย์ต้องใช้นิพจน์คงที่ และค่าของตัวแจงนับก็ต้องเป็นนิพจน์คงที่เช่นกัน
อย่างไรก็ตาม นิพจน์ค่าคงที่นั้นไม่ได้รับอนุญาตให้มีการเรียกใช้ฟังก์ชันหรือตัวสร้างอ็อบเจ็กต์ ดังนั้นโค้ดง่ายๆ แบบนี้จึงไม่ถูกต้อง:
int getFive () { return 5 ; }int someValue [ getFive () + 7 ]; // สร้างอาร์เรย์ของจำนวนเต็ม 12 ตัว (โค้ด C++ ที่ไม่ถูกต้อง)วิธีการนี้ใช้ไม่ได้ใน C++03 เพราะgetFive() + 7ไม่ใช่ค่าคงที่ คอมไพเลอร์ของ C++03 ไม่มีทางรู้ว่าค่านี้getFive()เป็นค่าคงที่จริง ๆ ในขณะรันไทม์หรือไม่ ในทางทฤษฎี ฟังก์ชันนี้อาจส่งผลต่อตัวแปรส่วนกลาง เรียกใช้ฟังก์ชันอื่นที่ไม่ใช่ค่าคงที่ในขณะรันไทม์ เป็นต้น
C++11 ได้แนะนำคีย์เวิร์ดconstexprซึ่งช่วยให้ผู้ใช้รับประกันได้ว่าฟังก์ชันหรือตัวสร้างวัตถุเป็นค่าคงที่ในเวลาคอมไพล์[ 11 ]ตัวอย่างข้างต้นสามารถเขียนใหม่ได้ดังนี้:
constexpr int getFive () { return 5 ; }int someValue [ getFive () + 7 ]; // สร้างอาร์เรย์ของจำนวนเต็ม 12 ตัว (ถูกต้องตามหลัก C++11)วิธีนี้ช่วยให้คอมไพเลอร์เข้าใจและตรวจสอบได้ว่านี่getFive()คือค่าคงที่ที่กำหนดในขั้นตอนการคอมไพล์
การใช้constexpr`on` กับฟังก์ชันจะกำหนดข้อจำกัดบางประการเกี่ยวกับสิ่งที่ฟังก์ชันนั้นสามารถทำได้ ประการแรก ฟังก์ชันต้องมีประเภทการคืนค่าที่ไม่ใช่ `void` ประการที่สอง ตัวฟังก์ชันไม่สามารถประกาศตัวแปรหรือกำหนดประเภทใหม่ได้ ประการที่สาม ตัวฟังก์ชันอาจประกอบด้วยการประกาศ คำสั่ง `null` และคำสั่ง `return` เพียงคำสั่งเดียวเท่านั้น ต้องมีค่าอาร์กิวเมนต์อยู่จริง โดยหลังจากแทนที่อาร์กิวเมนต์แล้ว นิพจน์ในคำสั่ง `return` จะสร้างนิพจน์คงที่
ก่อน C++11 ค่าของตัวแปรสามารถใช้ในนิพจน์คงที่ได้ก็ต่อเมื่อตัวแปรนั้นถูกประกาศเป็น const มีตัวกำหนดค่าเริ่มต้นที่เป็นนิพจน์คงที่ และเป็นชนิดข้อมูลจำนวนเต็มหรือชนิดข้อมูลแจงนับเท่านั้น C++11 ได้ยกเลิกข้อจำกัดที่ว่าตัวแปรต้องเป็นชนิดข้อมูลจำนวนเต็มหรือชนิดข้อมูลแจงนับหากตัวแปรนั้นถูกกำหนดด้วยconstexprคีย์เวิร์ด:
constexpr double EARTH_GRAVITATIONAL_ACCELERATION = 9.8 ; constexpr double MOON_GRAVITATIONAL_ACCELERATION = EARTH_GRAVITATIONAL_ACCELERATION / 6.0 ;ตัวแปรข้อมูลดังกล่าวถือเป็นค่าคงที่โดยปริยาย และต้องมีตัวกำหนดค่าเริ่มต้นซึ่งต้องเป็นนิพจน์ค่าคงที่
ในการสร้างค่าข้อมูลนิพจน์คงที่จากประเภทที่ผู้ใช้กำหนดเอง สามารถประกาศคอนสตรัคเตอร์ได้โดย ใช้ ` constexprconstructor` constexprส่วนเนื้อหาของฟังก์ชันคอนสตรัคเตอร์สามารถมีได้เฉพาะการประกาศและคำสั่ง null เท่านั้น และไม่สามารถประกาศตัวแปรหรือกำหนดประเภทได้เหมือนกับconstexprฟังก์ชันทั่วไป จะต้องมีค่าอาร์กิวเมนต์อยู่เพื่อให้หลังจากแทนที่อาร์กิวเมนต์แล้ว สามารถกำหนดค่าเริ่มต้นให้กับสมาชิกของคลาสด้วยนิพจน์คงที่ได้ ส่วนดีสตรัคเตอร์สำหรับประเภทดังกล่าวจะต้องเป็นแบบไม่ซับซ้อน
โดยปกติแล้ว คอนสตรัคเตอร์สำเนาสำหรับชนิดข้อมูลที่มีconstexprคอนสตรัคเตอร์ใดๆ ควรถูกกำหนดให้เป็นconstexprคอนสตรัคเตอร์ด้วย เพื่อให้สามารถส่งคืนอ็อบเจ็กต์ของชนิดข้อมูลนั้นโดยค่าจากฟังก์ชัน constexpr ได้ ฟังก์ชันสมาชิกใดๆ ของคลาส เช่น คอนสตรัคเตอร์สำเนา โอเวอร์โหลดตัวดำเนินการ ฯลฯ สามารถประกาศเป็น constexpr ได้ constexprตราบใดที่ตรงตามข้อกำหนดสำหรับฟังก์ชัน constexpr วิธีนี้ช่วยให้คอมไพเลอร์สามารถคัดลอกอ็อบเจ็กต์ในระหว่างการคอมไพล์ ดำเนินการกับอ็อบเจ็กต์เหล่านั้น ฯลฯ ได้
หากเรียกใช้ฟังก์ชันหรือคอนสตรัคเตอร์ constexpr ด้วยอาร์กิวเมนต์ที่ไม่ใช่ค่าคงที่ การเรียกใช้จะทำงานเสมือนว่าฟังก์ชันนั้นไม่ใช่ constexpr และค่าที่ได้จะไม่ใช่ค่าคงที่ ในทำนองเดียวกัน หากนิพจน์ในคำสั่ง return ของฟังก์ชัน constexpr ไม่สามารถประเมินค่าเป็นค่าคงที่ได้ในการเรียกใช้ครั้งนั้น ผลลัพธ์ก็จะไม่ใช่ค่าคงที่เช่นกัน
constexprแตกต่างจากconstevalที่ถูกนำมาใช้ในC++20ตรงที่ แบบหลังจะต้องสร้างค่าคงที่ในขั้นตอนการคอมไพล์เสมอ ในขณะที่constexprไม่มีข้อจำกัดนี้
การแก้ไขนิยามของข้อมูลธรรมดาแบบเดิม
ใน C++03 คลาสหรือโครงสร้างจะต้องปฏิบัติตามกฎหลายข้อจึงจะถือว่าเป็น ประเภท ข้อมูลธรรมดา (POD) ประเภทที่ตรงตามคำจำกัดความนี้จะสร้างโครงสร้างอ็อบเจ็กต์ที่เข้ากันได้กับภาษา C และยังสามารถกำหนดค่าเริ่มต้นแบบสแตติกได้ด้วย มาตรฐาน C++03 มีข้อจำกัดเกี่ยวกับประเภทที่เข้ากันได้กับภาษา C หรือสามารถกำหนดค่าเริ่มต้นแบบสแตติกได้ แม้ว่าจะไม่มีเหตุผลทางเทคนิคใด ๆ ที่คอมไพเลอร์จะไม่ยอมรับโปรแกรมก็ตาม หากมีคนสร้างประเภท POD ใน C++03 และเพิ่มฟังก์ชันสมาชิกเสมือน ประเภทนี้จะไม่ใช่ประเภท POD อีกต่อไป ไม่สามารถกำหนดค่าเริ่มต้นแบบสแตติกได้ และจะไม่เข้ากันได้กับภาษา C แม้ว่าจะไม่มีการเปลี่ยนแปลงโครงสร้างหน่วยความจำก็ตาม
C++11 ผ่อนปรนกฎ POD หลายข้อ โดยแบ่งแนวคิด POD ออกเป็นสองแนวคิดที่แยกจากกัน คือแบบง่าย (trivial)และแบบเค้าโครงมาตรฐาน (standard-layout )
ประเภทข้อมูลที่เรียบง่ายสามารถกำหนดค่าเริ่มต้นแบบคงที่ได้ นอกจากนี้ยังหมายความว่าสามารถคัดลอกข้อมูลไปมาได้memcpyโดยไม่ต้องใช้คอนสตรัคเตอร์สำหรับการคัดลอก อายุการใช้งานของ ประเภทข้อมูล ที่เรียบง่ายจะเริ่มต้นเมื่อมีการกำหนดพื้นที่จัดเก็บข้อมูล ไม่ใช่เมื่อคอนสตรัคเตอร์ทำงานเสร็จสิ้น
คลาสหรือโครงสร้างแบบง่าย (trivial class or struct) ถูกนิยามว่ามีคุณสมบัติดังต่อไปนี้:
- มีคอนสตรัคเตอร์เริ่มต้นที่เรียบง่าย ซึ่งอาจใช้ไวยากรณ์คอนสตรัคเตอร์เริ่มต้น (
SomeConstructor() = default;) - มีคอนสตรัคเตอร์สำหรับการคัดลอกและย้ายข้อมูลแบบง่ายๆ ซึ่งอาจใช้ไวยากรณ์เริ่มต้นได้
- มีตัวดำเนินการกำหนดค่าแบบคัดลอกและย้ายที่ไม่ซับซ้อน ซึ่งอาจใช้ไวยากรณ์เริ่มต้นได้
- มีตัวทำลายแบบง่ายๆ ซึ่งต้องไม่ใช่แบบเสมือน
คอนสตรัคเตอร์จะเป็นแบบทริบิวต์ได้ก็ต่อเมื่อไม่มีฟังก์ชันสมาชิกเสมือนของคลาสและไม่มีคลาสฐานเสมือน นอกจากนี้ การดำเนินการคัดลอก/ย้ายข้อมูลยังต้องการให้สมาชิกข้อมูลที่ไม่ใช่แบบสแตติกทั้งหมดเป็นแบบทริบิวต์ด้วย
ประเภทข้อมูลที่มีโครงสร้างมาตรฐานหมายความว่ามันจัดเรียงและจัดเก็บสมาชิกในลักษณะที่เข้ากันได้กับภาษาซี คลาสหรือโครงสร้างข้อมูลจะมีโครงสร้างมาตรฐานตามคำนิยามก็ต่อเมื่อ:
- ไม่มีฟังก์ชันเสมือนจริง
- ไม่มีคลาสฐานเสมือน
- สมาชิกข้อมูลที่ไม่ใช่แบบคงที่ทั้งหมดมีการควบคุมการเข้าถึงแบบเดียวกัน (สาธารณะ ส่วนตัว ป้องกัน)
- สมาชิกข้อมูลที่ไม่ใช่แบบสแตติกทั้งหมด รวมถึงสมาชิกข้อมูลในคลาสพื้นฐาน ล้วนอยู่ในคลาสเดียวกันในลำดับชั้น
- กฎข้างต้นยังใช้ได้กับคลาสพื้นฐานทั้งหมดและสมาชิกข้อมูลที่ไม่ใช่แบบสแตติกทั้งหมดในลำดับชั้นของคลาสด้วย
- ไม่มีคลาสพื้นฐานประเภทเดียวกันกับสมาชิกข้อมูลที่ไม่ใช่แบบคงที่ตัวแรกที่กำหนดไว้
คลาส/โครงสร้าง/ยูเนียนจะถือว่าเป็น POD (Proof of Design) ก็ต่อเมื่อมันเรียบง่าย มีโครงสร้างมาตรฐาน และสมาชิกข้อมูลที่ไม่ใช่แบบสแตติกทั้งหมด รวมถึงคลาสพื้นฐานทั้งหมดเป็น POD ด้วย
โดยการแยกแนวคิดเหล่านี้ออกจากกัน ก็สามารถละทิ้งแนวคิดหนึ่งได้โดยไม่สูญเสียอีกแนวคิดหนึ่งไป คลาสที่มีคอนสตรัคเตอร์สำหรับการย้ายและการคัดลอกที่ซับซ้อนอาจไม่ใช่คลาสที่เรียบง่าย แต่ก็อาจมีโครงสร้างมาตรฐานและสามารถทำงานร่วมกับภาษา C ได้ ในทำนองเดียวกัน คลาสที่มีสมาชิกข้อมูลแบบสาธารณะและส่วนตัวที่ไม่ใช่แบบสแตติกอาจไม่ใช่โครงสร้างมาตรฐาน แต่ก็อาจเป็นคลาสที่เรียบง่ายและmemcpyสามารถทำงานร่วมกับภาษา C ได้
การปรับปรุงประสิทธิภาพการสร้างภาษาหลัก
เทมเพลตภายนอก
ใน C++03 คอมไพเลอร์จะต้องสร้างอินสแตนซ์ของเทมเพลตทุกครั้งที่พบเทมเพลตที่ระบุอย่างครบถ้วนในหน่วยการแปล หากเทมเพลตถูกสร้างอินสแตนซ์ด้วยชนิดข้อมูลเดียวกันในหลายหน่วยการแปล อาจทำให้เวลาในการคอมไพล์เพิ่มขึ้นอย่างมาก ไม่มีวิธีใดที่จะป้องกันปัญหานี้ได้ใน C++03 ดังนั้น C++11 จึงได้แนะนำการประกาศเทมเพลตแบบ extern ซึ่งคล้ายกับการประกาศข้อมูลแบบ extern
C++03 มีไวยากรณ์นี้เพื่อบังคับให้คอมไพเลอร์สร้างอินสแตนซ์ของเทมเพลต:
template class std :: vector <MyClass> ;C++11 รองรับไวยากรณ์นี้แล้ว:
extern template class std :: vector <MyClass> ;ซึ่งจะบอกคอมไพเลอร์ว่าไม่ต้องสร้างอินสแตนซ์ของเทมเพลตในหน่วยการแปลนี้
การปรับปรุงการใช้งานภาษาหลัก
คุณสมบัติเหล่านี้มีจุดประสงค์หลักเพื่อทำให้ภาษาใช้งานง่ายขึ้น ซึ่งสามารถปรับปรุงความปลอดภัยของชนิดข้อมูล ลดการเขียนโค้ดซ้ำซ้อน ลดโอกาสเกิดโค้ดผิดพลาด ฯลฯ
รายการตัวเริ่มต้น
C++03 สืบทอดคุณสมบัติรายการเริ่มต้น (initializer-list) มาจากภาษา C โดยโครงสร้าง (struct) หรืออาร์เรย์ (array) จะได้รับรายการอาร์กิวเมนต์ในวงเล็บปีกกา ตามลำดับของการกำหนดค่าสมาชิกในโครงสร้างนั้น รายการเริ่มต้นเหล่านี้เป็นแบบเรียกซ้ำ ดังนั้นอาร์เรย์ของโครงสร้างหรือโครงสร้างที่ประกอบด้วยโครงสร้างอื่น ๆ สามารถใช้งานได้
struct FloatAndInt { float first ; int second ; };FloatAndInt scalar = { 0.43f , 10 }; // อ็อบเจ็กต์หนึ่งตัว โดยที่ตัวแรกคือ 0.43f และตัวที่สองคือ 10 FloatAndInt anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // อาร์เรย์ของอ็อบเจ็กต์สามตัวสิ่งนี้มีประโยชน์มากสำหรับลิสต์แบบคงที่ หรือการกำหนดค่าเริ่มต้นให้กับโครงสร้าง (struct) C++ ยังมีคอนสตรัคเตอร์สำหรับกำหนดค่าเริ่มต้นให้กับอ็อบเจ็กต์ แต่โดยทั่วไปแล้วจะไม่สะดวกเท่ากับลิสต์กำหนดค่าเริ่มต้น อย่างไรก็ตาม C++03 อนุญาตให้ใช้ลิสต์กำหนดค่าเริ่มต้นได้เฉพาะกับโครงสร้างและคลาสที่สอดคล้องกับคำจำกัดความของ Plain Old Data (POD) เท่านั้น C++11 ขยายลิสต์กำหนดค่าเริ่มต้นเพื่อให้สามารถใช้ได้กับทุกคลาส รวมถึงคอนเทนเนอร์มาตรฐาน เช่นstd::vector.
C++11 ผูกแนวคิดนี้เข้ากับเทมเพลตที่เรียกว่าstd::initializer_list`initializer` ซึ่งช่วยให้คอนสตรัคเตอร์และฟังก์ชันอื่นๆ สามารถรับลิสต์เริ่มต้นเป็นพารามิเตอร์ได้ ตัวอย่างเช่น:
โดยใช้std :: initializer_list ;คลาสSequenceClass { public : SequenceClass ( initializer_list < int > list ); };วิธีนี้ช่วยให้SequenceClassสามารถสร้างขึ้นจากลำดับของจำนวนเต็มได้ เช่น:
SequenceClass someSeq = { 1 , 4 , 5 , 6 };คอนสตรัคเตอร์นี้เป็นคอนสตรัคเตอร์ชนิดพิเศษ เรียกว่า คอนสตรัคเตอร์รายการเริ่มต้น (initializer-list-constructor) คลาสที่มีคอนสตรัคเตอร์ประเภทนี้จะได้รับการจัดการเป็นพิเศษในระหว่างการเริ่มต้นแบบสม่ำเสมอ (ดูด้านล่าง )
คลาสเทมเพลตstd::initializer_list<>เป็น ประเภทไลบรารีมาตรฐาน ชั้นหนึ่งของ C++11 สามารถสร้างได้แบบสแตติกโดยคอมไพเลอร์ C++11 โดยใช้ไวยากรณ์โดย{}ไม่ต้องระบุชื่อประเภทในบริบทที่วงเล็บปีกกาจะอนุมานเป็นstd::initializer_listหรือโดยการระบุประเภทอย่างชัดเจน เช่นstd::initializer_list<SomeType>{args}(และอื่นๆ สำหรับไวยากรณ์การสร้างแบบอื่นๆ)
สามารถคัดลอกลิสต์ได้เมื่อสร้างเสร็จแล้ว ซึ่งมีต้นทุนต่ำและจะทำหน้าที่เหมือนการคัดลอกโดยอ้างอิง (โดยทั่วไปคลาสจะถูกนำไปใช้ในรูปแบบของตัวชี้เริ่มต้น/สิ้นสุดสองตัว) ส่วนลิสต์นั้นstd::initializer_listเป็นค่าคงที่: สมาชิกของลิสต์ไม่สามารถเปลี่ยนแปลงได้เมื่อสร้างขึ้นแล้ว และข้อมูลในสมาชิกเหล่านั้นก็ไม่สามารถเปลี่ยนแปลงได้เช่นกัน (ซึ่งทำให้ไม่สามารถย้ายข้อมูลจากสมาชิกเหล่านั้นไปยังสมาชิกของคลาสได้ เป็นต้น)
แม้ว่าโครงสร้างของมันจะได้รับการจัดการเป็นพิเศษโดยคอมไพเลอร์ แต่std::initializer_listก็เป็นชนิดข้อมูลจริง ดังนั้นจึงสามารถนำไปใช้ในที่อื่นๆ นอกเหนือจากตัวสร้างคลาสได้ ฟังก์ชันทั่วไปสามารถรับ ที่มีชนิดข้อมูลstd::initializer_listเป็นอาร์กิวเมนต์ได้ ตัวอย่างเช่น:
โดยใช้std :: initializer_list ;void functionName ( initializer_list < float > list ); // การคัดลอกทำได้ง่ายและรวดเร็ว ดูด้านบนfunctionName ({ 1.0f , -3.45f , -0.4f });ตัวอย่างของสิ่งนี้ในไลบรารีมาตรฐาน ได้แก่std::min()เทมเพลตstd::max()ที่รับstd::initializer_listค่าประเภทตัวเลข
นอกจากนี้ ยังสามารถเริ่มต้นใช้งานคอนเทนเนอร์มาตรฐานได้ด้วยวิธีเหล่านี้:
using std :: string ; using std :: vector ;vector < string > v = { "xyzzy" , "plugh" , "abracadabra" }; vector < string > v ({ "xyzzy" , "plugh" , "abracadabra" }); vector < string > v { "xyzzy" , "plugh" , "abracadabra" }; // ดู "การเริ่มต้นแบบสม่ำเสมอ" ด้านล่างการเริ่มต้นแบบสม่ำเสมอ
C++03 มีปัญหาหลายอย่างเกี่ยวกับการกำหนดค่าเริ่มต้นให้กับชนิดข้อมูล มีหลายวิธีในการทำเช่นนี้ และบางวิธีก็ให้ผลลัพธ์ที่แตกต่างกันเมื่อสลับกันใช้ ตัวอย่างเช่น ไวยากรณ์ของคอนสตรัคเตอร์แบบดั้งเดิมอาจดูเหมือนการประกาศฟังก์ชัน และต้องมีขั้นตอนเพื่อให้แน่ใจว่า กฎ การวิเคราะห์ที่ซับซ้อนที่สุด ของคอมไพเลอร์ จะไม่เข้าใจผิดว่าเป็นเช่นนั้น เฉพาะชนิดข้อมูลแบบ Aggregate และ POD เท่านั้นที่สามารถกำหนดค่าเริ่มต้นได้ด้วยตัวกำหนดค่าเริ่มต้นแบบ Aggregate (โดยใช้SomeType var = {/*stuff*/};)
C++11 มีไวยากรณ์ที่ช่วยให้สามารถกำหนดค่าเริ่มต้นของประเภทได้อย่างสม่ำเสมอและใช้งานได้กับวัตถุทุกประเภท โดยเป็นการต่อยอดจากไวยากรณ์รายการตัวกำหนดค่าเริ่มต้น:
struct IntAndFloat1 { int x ; double y ; };struct IntAndFloat2 { private : int x ; double y ; public : IntAndFloat2 ( int x , double y ) : x { x }, y { y } {} };IntAndFloat1 var1 { 5 , 3.2 }; IntAndFloat2 var2 { 2 , 4.3 };การเริ่มต้นใช้var1งานจะทำงานเหมือนกับการเริ่มต้นใช้งานแบบรวมกลุ่ม กล่าวคือ สมาชิกข้อมูลแต่ละตัวของอ็อบเจ็กต์จะถูกคัดลอกและเริ่มต้นด้วยค่าที่สอดคล้องกันจากรายการเริ่มต้นใช้งาน การแปลงประเภทโดยปริยายจะถูกใช้เมื่อจำเป็น หากไม่มีการแปลงประเภท หรือมีการแปลงประเภทแบบจำกัดเท่านั้น โปรแกรมจะไม่ถูกต้อง การเริ่มต้นใช้งานจะเรียกใช้var2คอนสตรัคเตอร์
นอกจากนี้ยังสามารถทำแบบนี้ได้ด้วย:
โดยใช้std :: string ;struct IdString { string name ; int identifier ; };IdString getString () { return { "foo" , 42 }; // โปรดสังเกตว่าไม่มีการระบุประเภทอย่างชัดเจน}การกำหนดค่าเริ่มต้นแบบสม่ำเสมอไม่ได้มาแทนที่ไวยากรณ์ของคอนสตรัคเตอร์ ซึ่งยังคงจำเป็นในบางครั้ง หากคลาสมีคอนสตรัคเตอร์รายการเริ่มต้น ( TypeName(initializer_list<SomeType>);) คอนสตรัคเตอร์นั้นจะมีลำดับความสำคัญเหนือกว่ารูปแบบการสร้างอื่นๆ โดยมีเงื่อนไขว่ารายการเริ่มต้นต้องสอดคล้องกับประเภทของคอนสตรัคเตอร์ลำดับ เวอร์ชัน C++11 ของstd::vectorมีคอนสตรัคเตอร์รายการเริ่มต้นสำหรับประเภทเทมเพลต ดังนั้นโค้ดนี้:
std :: vector <int> vec {4} ;จะเรียกใช้คอนสตรัคเตอร์ของรายการเริ่มต้น ไม่ใช่คอนสตรัคเตอร์std::vectorที่รับพารามิเตอร์ขนาดเพียงตัวเดียวและสร้างเวกเตอร์ที่มีขนาดนั้น หากต้องการเข้าถึงคอนสตรัคเตอร์หลัง ผู้ใช้จะต้องใช้ไวยากรณ์คอนสตรัคเตอร์มาตรฐานโดยตรง
การอนุมานประเภท
ใน C++03 (และ C) การใช้ตัวแปรจำเป็นต้องระบุชนิดของตัวแปรนั้นอย่างชัดเจน อย่างไรก็ตาม ด้วยการเกิดขึ้นของชนิดเทมเพลตและเทคนิคเมตาโปรแกรมมิงแบบเทมเพลต ชนิดของบางสิ่ง โดยเฉพาะอย่างยิ่งค่าส่งคืนที่กำหนดไว้อย่างชัดเจนของฟังก์ชัน อาจไม่สามารถแสดงออกมาได้ง่าย ดังนั้น การจัดเก็บค่ากลางในตัวแปรจึงทำได้ยาก อาจต้องอาศัยความรู้เกี่ยวกับกลไกภายในของไลบรารีเมตาโปรแกรมมิงที่ใช้
C++11 อนุญาตให้บรรเทาปัญหานี้ได้สองวิธี วิธีแรกคือ การกำหนดตัวแปรด้วยการเริ่มต้นแบบชัดเจนสามารถใช้autoคีย์เวิร์ด ได้ [ 12 ] [ 13 ]ซึ่งจะสร้างตัวแปรประเภทเฉพาะของตัวเริ่มต้น:
auto some_strange_callable_type = std :: bind ( & some_function , _2 , _1 , some_object ); auto other_variable = 5 ;ชนิดของข้อมูลนั้นsome_strange_callable_typeก็คือค่าที่ฟังก์ชันเทมเพลตเฉพาะนั้นส่งstd::bindคืนสำหรับอาร์กิวเมนต์เหล่านั้น ชนิดนี้สามารถตรวจสอบได้ง่ายโดยคอมไพเลอร์ในขั้นตอนการวิเคราะห์ความหมาย แต่ผู้ใช้ไม่สามารถตรวจสอบได้ง่ายด้วยการตรวจสอบเพียงอย่างเดียว ชนิดของข้อมูลother_variableก็ถูกกำหนดไว้อย่างชัดเจนเช่นกัน แต่ผู้ใช้ตรวจสอบได้ง่ายกว่า มันคือชนิดข้อมูล แบบจำนวนเต็ม (integral type int) ซึ่งเป็นชนิดเดียวกับตัวเลขจำนวนเต็ม
การใช้คีย์เวิร์ดนี้autoใน C++ เป็นการนำความหมายของคีย์เวิร์ดนี้มาใช้ใหม่ ซึ่งเดิมทีคีย์เวิร์ดนี้ถูกใช้ในภาษาB ซึ่งเป็นภาษารุ่นก่อนหน้าที่ไม่มีการกำหนดประเภทข้อมูล ในบทบาทที่เกี่ยวข้องกับการบ่งบอกถึงการ นิยาม ตัวแปรอัตโนมัติ ที่ไม่มี การกำหนด ประเภทข้อมูล
นอกจากนี้ คำหลักdecltypeยังสามารถใช้เพื่อกำหนดประเภทของนิพจน์ในระหว่างการคอมไพล์ได้ ตัวอย่างเช่น:
int some_int ; decltype ( some_int ) other_integer_variable = 5 ;วิธีนี้มีประโยชน์มากกว่าเมื่อใช้ร่วมกับautoเนื่องจากชนิดของตัวแปรอัตโนมัติเป็นที่รู้จักเฉพาะคอมไพเลอร์เท่านั้น อย่างไรก็ตาม วิธีนี้decltypeยังมีประโยชน์มากสำหรับนิพจน์ในโค้ดที่ใช้การโอเวอร์โหลดตัวดำเนินการและชนิดข้อมูลเฉพาะ อย่างมาก
autoนอกจากนี้ยังช่วยลดความซับซ้อนของโค้ดได้อีกด้วย ตัวอย่างเช่น แทนที่จะเขียนแบบนี้
for ( std :: vector < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )โปรแกรมเมอร์สามารถใช้รูปแบบที่สั้นกว่าได้
สำหรับ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )ซึ่งสามารถย่อให้กระชับยิ่งขึ้นได้ เนื่องจาก "myvec" มีการใช้งานตัววนซ้ำเริ่มต้น/สิ้นสุด:
สำหรับ( const auto & x : myvec )ความแตกต่างนี้จะเพิ่มมากขึ้นเมื่อโปรแกรมเมอร์เริ่มใช้คอนเทนเนอร์แบบซ้อนกัน แต่ในกรณีเช่นนั้น การใช้typedef`s` ก็เป็นวิธีที่ดีในการลดปริมาณโค้ดลง
ประเภทที่ระบุโดยdecltypeอาจแตกต่างจากประเภทที่อนุมานได้autoจาก
#รวม<เวกเตอร์>โดยใช้std :: vector ;int main () { const vector < int > v ( 1 ); auto a = v [ 0 ]; // a มีชนิดเป็น int decltype ( v [ 0 ]) b = 1 ; // b มีชนิดเป็น const int& ซึ่งเป็นชนิดค่าส่งคืนของ// std::vector<int>::operator[](size_type) const auto c = 0 ; // c มีชนิดเป็น int auto d = c ; // d มีชนิดเป็น int decltype ( c ) e ; // e มีชนิดเป็น int ซึ่งเป็นชนิดของเอนทิตีที่ตั้งชื่อโดย c decltype (( c )) f = c ; // f มีชนิดเป็น int& เพราะ (c) เป็น lvalue decltype ( 0 ) g ; // g มีชนิดเป็น int เพราะ 0 เป็น rvalue }ลูป for แบบอิงช่วง
C++11 ขยายไวยากรณ์ของforคำสั่งเพื่อให้สามารถวนซ้ำช่วงขององค์ประกอบได้อย่างง่ายดาย:
int a [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; // คูณค่าของแต่ละองค์ประกอบใน a ด้วย 2: for ( int & x : a ) { x *= 2 ; }// คล้ายกัน แต่ยังใช้การอนุมานประเภทสำหรับองค์ประกอบอาร์เรย์ด้วยสำหรับ( auto & x : a ) { x *= 2 ; }รูปแบบนี้forเรียกว่า “for แบบอิงช่วง” จะวนซ้ำไปทีละองค์ประกอบในรายการ มันใช้งานได้กับอาร์เรย์แบบ C (โดยที่ทราบความยาวของมันในระหว่างการคอมไพล์) รายการเริ่มต้น และประเภทใดๆ ก็ตามที่มีฟังก์ชัน `begin` และ ` end` begin()ที่end()กำหนดไว้สำหรับมันซึ่งส่งคืนตัววนซ้ำ คอนเทนเนอร์ไลบรารีมาตรฐานทั้งหมดที่มีคู่ `begin`/`end` จะใช้งานได้กับคำสั่ง `for แบบอิงช่วง`
ฟังก์ชันและนิพจน์แลมบ์ดา
C++11 มีความสามารถในการสร้างฟังก์ชันนิรนามที่เรียกว่าฟังก์ชันแลมบ์ดา[ 14 ] ซึ่งกำหนดไว้ดังนี้:
[]( int x , int y ) -> int { return x + y ; }ประเภทค่าส่งคืน ( -> intในตัวอย่างนี้) สามารถละเว้นได้ตราบใดที่returnนิพจน์ทั้งหมดส่งคืนประเภทเดียวกัน แลมบ์ดาสามารถเป็นโคลเชอร์ได้ หรือไม่ก็ได้
ไวยากรณ์ฟังก์ชันทางเลือก
ไวยากรณ์การประกาศฟังก์ชัน มาตรฐานของภาษา Cนั้นเหมาะสมอย่างยิ่งสำหรับชุดคุณสมบัติของภาษา C เมื่อ C++ พัฒนามาจาก C มันยังคงรักษาไวยากรณ์พื้นฐานไว้และขยายเพิ่มเติมเมื่อจำเป็น อย่างไรก็ตาม เมื่อ C++ มีความซับซ้อนมากขึ้น มันก็เผยให้เห็นข้อจำกัดหลายประการ โดยเฉพาะอย่างยิ่งเกี่ยวกับการประกาศฟังก์ชันแบบเทมเพลต ตัวอย่างเช่น ใน C++03 การประกาศแบบนี้ไม่ถูกต้อง:
template < class L , class R > Ret add ( const L & lhs , const R & rhs ) { return lhs + rhs ; // Ret ต้องเป็นชนิดเดียวกับ lhs + rhs }ประเภทนั้นRetจะเป็นผลรวมของประเภทต่างๆLและRจะสร้างผลลัพธ์ออกมา แม้จะมีฟังก์ชันการทำงานของ C++11 ดังกล่าวข้างต้น ก็ยังdecltypeไม่สามารถทำเช่นนี้ได้:
// ไม่ถูกต้องใน C++11 template < class L , class R > decltype ( lhs + rhs ) add ( const L & lhs , const R & rhs ) { return lhs + rhs ; }นี่ไม่ใช่โค้ด C++ ที่ถูกต้อง เนื่องจากlhsและrhsยังไม่ได้ถูกกำหนดไว้ และตัวระบุเหล่านี้จะไม่สามารถใช้งานได้จนกว่าตัวแยกวิเคราะห์จะแยกวิเคราะห์ส่วนที่เหลือของต้นแบบฟังก์ชันเสร็จแล้ว
เพื่อแก้ไขปัญหานี้ C++11 จึงได้แนะนำไวยากรณ์การประกาศฟังก์ชันใหม่ที่มีประเภทการคืนค่าท้ายสุด : [ 15 ]
template < class L , class R > auto add ( const L & lhs , const R & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }สามารถใช้ไวยากรณ์นี้สำหรับการประกาศและการกำหนดค่าฟังก์ชันทั่วไปได้:
struct SomeStruct { auto add ( int x , int y ) -> int { return x + y ; } };การใช้autoคีย์เวิร์ด " " ในกรณีนี้เป็นเพียงส่วนหนึ่งของไวยากรณ์และไม่ได้ทำการอนุมานประเภทโดยอัตโนมัติใน C++11 อย่างไรก็ตาม ตั้งแต่ C++14 เป็นต้นไป สามารถลบประเภทการส่งคืนที่ต่อท้ายออกได้ทั้งหมด และคอมไพเลอร์จะอนุมานประเภทการส่งคืนโดยอัตโนมัติ[ 16 ]
การปรับปรุงการก่อสร้างวัตถุ
ใน C++03 คอนสตรัคเตอร์ของคลาสไม่ได้รับอนุญาตให้เรียกคอนสตรัคเตอร์อื่นในรายการเริ่มต้นของคลาสนั้น คอนสตรัคเตอร์แต่ละตัวต้องสร้างสมาชิกทั้งหมดของคลาสด้วยตนเอง หรือเรียกฟังก์ชันสมาชิกทั่วไป ดังต่อไปนี้:
คลาสInteger { private : int value ;void setValue ( int num ) { value = num ; } public : Integer ( int number ) { setValue ( number ); }จำนวนเต็ม() { setValue ( 42 ); } };คอนสตรัคเตอร์ของคลาสพื้นฐานไม่สามารถเปิดเผยโดยตรงให้กับคลาสที่สืบทอดได้ แต่ละคลาสที่สืบทอดจะต้องสร้างคอนสตรัคเตอร์ของตนเอง แม้ว่าคอนสตรัคเตอร์ของคลาสพื้นฐานจะเหมาะสมกว่าก็ตาม สมาชิกข้อมูลที่ไม่ใช่ค่าคงที่ของคลาสไม่สามารถกำหนดค่าเริ่มต้นได้ ณ จุดที่ประกาศสมาชิกเหล่านั้น สามารถกำหนดค่าเริ่มต้นได้เฉพาะในคอนสตรัคเตอร์เท่านั้น
C++11 มีวิธีแก้ปัญหาเหล่านี้ทั้งหมด
C++11 อนุญาตให้คอนสตรัคเตอร์เรียกคอนสตรัคเตอร์อื่นที่มีประเภทเดียวกันได้ (เรียกว่าการมอบหมายอำนาจ ) ซึ่งช่วยให้คอนสตรัคเตอร์สามารถใช้ประโยชน์จากพฤติกรรมของคอนสตรัคเตอร์อื่นได้โดยใช้โค้ดเพิ่มเติมเพียงเล็กน้อย การมอบหมายอำนาจนี้ถูกนำไปใช้ในภาษาโปรแกรมอื่นๆ เช่นJavaและObjective-Cแล้ว
รูปแบบไวยากรณ์มีดังนี้:
คลาสInteger { private : int value ; public : Integer ( int number ) : value ( number ) {} }จำนวนเต็ม() : จำนวนเต็ม( 42 ) {} };ในกรณีนี้ ผลลัพธ์เดียวกันสามารถทำได้โดยการสร้างnumberพารามิเตอร์เริ่มต้น อย่างไรก็ตาม ไวยากรณ์ใหม่นี้อนุญาตให้แสดงค่าเริ่มต้น (42) ในการใช้งานแทนที่จะเป็นอินเทอร์เฟซ ซึ่งเป็นประโยชน์ต่อผู้ดูแลโค้ดไลบรารี เนื่องจากค่าเริ่มต้นสำหรับพารามิเตอร์ฟังก์ชันจะถูก "ฝัง" ไว้ในไซต์การเรียก ในขณะที่การมอบหมายตัวสร้างอนุญาตให้เปลี่ยนค่าได้โดยไม่ต้องคอมไพล์โค้ดใหม่โดยใช้ไลบรารี
อย่างไรก็ตาม มีข้อควรระวังอยู่อย่างหนึ่งคือ C++03 ถือว่าวัตถุถูกสร้างขึ้นเมื่อคอนสตรัคเตอร์ของวัตถุนั้นทำงานเสร็จสิ้น แต่ C++11 ถือว่าวัตถุถูกสร้างขึ้นเมื่อ คอนสตรัคเตอร์ ใดๆ ก็ตามทำงานเสร็จสิ้น เนื่องจากจะอนุญาตให้มีคอนสตรัคเตอร์หลายตัวทำงานได้ นั่นหมายความว่าคอนสตรัคเตอร์แต่ละตัวที่ทำหน้าที่ส่งต่อการทำงานจะทำงานกับวัตถุที่สร้างเสร็จสมบูรณ์แล้วของประเภทนั้นๆ คอนสตรัคเตอร์ของคลาสที่สืบทอดจะทำงานหลังจากที่การส่งต่อการทำงานในคลาสพื้นฐานเสร็จสมบูรณ์แล้ว
สำหรับคอนสตรัคเตอร์ของคลาสพื้นฐาน C++11 อนุญาตให้คลาสระบุว่าคอนสตรัคเตอร์ของคลาสพื้นฐานจะถูกสืบทอด ดังนั้น คอมไพเลอร์ C++11 จะสร้างโค้ดเพื่อทำการสืบทอดและการส่งต่อคลาสที่สืบทอดไปยังคลาสพื้นฐาน คุณสมบัตินี้เป็นแบบทั้งหมดหรือไม่มีเลย: คอนสตรัคเตอร์ทั้งหมดของคลาสพื้นฐานนั้นจะถูกส่งต่อหรือไม่ถูกส่งต่อเลย นอกจากนี้ คอนสตรัคเตอร์ที่สืบทอดมาจะถูกซ่อนไว้หากมีลายเซ็นตรงกับคอนสตรัคเตอร์ของคลาสที่สืบทอดมา และมีข้อจำกัดสำหรับการสืบทอดแบบหลายทาง: คอนสตรัคเตอร์ของคลาสไม่สามารถสืบทอดจากสองคลาสที่ใช้คอนสตรัคเตอร์ที่มีลายเซ็นเดียวกันได้
รูปแบบการเขียนมีดังนี้:
คลาสBaseClass { public : BaseClass ( int value ); };class DerivedClass : public BaseClass { public : using BaseClass :: BaseClass ; };สำหรับการกำหนดค่าเริ่มต้นให้กับสมาชิก C++11 อนุญาตให้ใช้ไวยากรณ์ดังนี้:
คลาสInteger { private : int value = 5 ; public : Integer () {} }explicit Integer ( int value ) : value ( value ) {} };คอนสตรัคเตอร์ใดๆ ของคลาสจะกำหนดค่าเริ่มต้นvalueเป็น 5 หากคอนสตรัคเตอร์นั้นไม่ได้กำหนดค่าเริ่มต้นใหม่ด้วยค่าของตัวเอง ดังนั้นคอนสตรัคเตอร์ว่างเปล่าข้างต้นจะกำหนดค่าเริ่มต้นvalueตามที่ระบุไว้ในนิยามของคลาส แต่คอนสตรัคเตอร์ที่รับค่า int จะกำหนดค่าเริ่มต้นเป็นค่าที่กำหนดให้
นอกจากนี้ยังสามารถใช้การกำหนดค่าเริ่มต้นผ่านคอนสตรัคเตอร์หรือแบบยูนิฟอร์ม แทนการกำหนดค่าเริ่มต้นแบบแสดงข้างต้นได้อีกด้วย
การแทนที่โดยชัดแจ้งและขั้นสุดท้าย
ใน C++03 อาจเกิดการสร้างฟังก์ชันเสมือนใหม่โดยไม่ได้ตั้งใจ เมื่อตั้งใจจะเขียนทับฟังก์ชันของคลาสพื้นฐาน ตัวอย่างเช่น:
struct Base { virtual void someFunction ( float ); };struct Derived : public Base { virtual void someFunction ( int ); };สมมติว่าฟังก์ชันนี้Derived::someFunctionตั้งใจจะใช้แทนฟังก์ชันเวอร์ชันของคลาสพื้นฐาน แต่เนื่องจากมีรูปแบบการเรียกใช้ ที่แตกต่างกัน จึงทำให้เกิดฟังก์ชันเสมือนตัวที่สองขึ้นมา นี่เป็นปัญหาที่พบได้บ่อย โดยเฉพาะอย่างยิ่งเมื่อผู้ใช้ต้องการแก้ไขคลาสพื้นฐาน
C++11 มีไวยากรณ์ที่ช่วยแก้ปัญหานี้ได้
struct Base { virtual void someFunction ( float ); };struct Derived : public Base { virtual void someFunction ( int ) override ; // รูปแบบไม่ถูกต้อง - ไม่ได้เขียนทับเมธอดของคลาสพื้นฐาน};ตัวระบุพิเศษ นี้overrideหมายความว่าคอมไพเลอร์จะตรวจสอบคลาสพื้นฐานเพื่อดูว่ามีฟังก์ชันเสมือนที่มีลายเซ็นตรงกันนี้หรือไม่ และหากไม่มี คอมไพเลอร์จะแสดงข้อผิดพลาด
C++11 ยังเพิ่มความสามารถในการป้องกันการสืบทอดจากคลาส หรือป้องกันการเขียนทับเมธอดในคลาสที่สืบทอดมาได้อีกด้วย ทำได้โดยใช้ตัวระบุพิเศษ `@` finalตัวอย่างเช่น:
struct Base final { // ... };// ไม่ถูกต้องเนื่องจากคลาส Base1 ถูกทำเครื่องหมายเป็น final struct Derived : public Base { // ... };struct Base { virtual void f () final ; };struct Derived : public Base { void f (); // รูปแบบไม่ถูกต้องเนื่องจากฟังก์ชันเสมือน Base::f ถูกทำเครื่องหมายเป็น final };ในตัวอย่างนี้virtual void f() final;คำสั่งดังกล่าวประกาศฟังก์ชันเสมือนใหม่ แต่ยังป้องกันไม่ให้คลาสที่สืบทอดเขียนทับฟังก์ชันนั้นได้ นอกจากนี้ยังมีผลในการป้องกันไม่ให้คลาสที่สืบทอดใช้ชื่อฟังก์ชันและพารามิเตอร์ชุดนั้นโดยเฉพาะอีกด้วย
ทั้งสองคำนี้overrideไม่ใช่finalคำหลักของภาษา แต่เป็นตัวระบุคุณลักษณะของผู้ประกาศในทางเทคนิค:
- คุณลักษณะเหล่านี้จะมีความหมายพิเศษก็ต่อเมื่อใช้ในบริบทเฉพาะเหล่านั้น (หลังตัวระบุประเภท ตัวระบุการเข้าถึง การประกาศสมาชิก (สำหรับประเภทโครงสร้าง คลาส และ enum) และตัวระบุผู้ประกาศ แต่ก่อนการเริ่มต้นหรือการใช้งานโค้ดของผู้ประกาศแต่ละคนในรายการผู้ประกาศที่คั่นด้วยเครื่องหมายจุลภาค)
- ฟังก์ชันเหล่านี้จะไม่เปลี่ยนแปลงลายเซ็นประเภทที่ประกาศไว้ และจะไม่ประกาศหรือแทนที่ตัวระบุใหม่ใดๆ ในขอบเขตใดๆ
- คุณลักษณะตัวประกาศที่ได้รับการยอมรับและรู้จักอาจได้รับการขยายเพิ่มเติมในเวอร์ชัน C++ ในอนาคต (ส่วนขยายเฉพาะคอมไพเลอร์บางส่วนรู้จักคุณลักษณะตัวประกาศที่เพิ่มเข้ามาแล้ว เพื่อให้ตัวเลือกการสร้างโค้ดหรือคำแนะนำในการเพิ่มประสิทธิภาพแก่คอมไพเลอร์ หรือเพื่อสร้างข้อมูลเพิ่มเติมลงในโค้ดที่คอมไพล์แล้ว ซึ่งมีไว้สำหรับดีบักเกอร์ ลิงเกอร์ และการใช้งานโค้ดที่คอมไพล์แล้ว หรือเพื่อให้คุณลักษณะด้านความปลอดภัยเฉพาะระบบเพิ่มเติม หรือเพื่อเพิ่มประสิทธิภาพ ความสามารถ ในการเขียนโปรแกรมแบบสะท้อน (reflection) ในขณะรันไทม์ หรือเพื่อให้ข้อมูลการผูกเพิ่มเติมสำหรับการทำงานร่วมกันกับภาษาการเขียนโปรแกรมและระบบรันไทม์อื่นๆ ส่วนขยายเหล่านี้อาจรับพารามิเตอร์ระหว่างวงเล็บหลังตัวระบุคุณลักษณะตัวประกาศ สำหรับการปฏิบัติตามมาตรฐาน ANSI ส่วนขยายเฉพาะคอมไพเลอร์เหล่านี้ควรใช้รูปแบบคำนำหน้าด้วยเครื่องหมายขีดล่างสองตัว)
- ในสถานที่อื่นๆ พวกมันสามารถใช้เป็นตัวระบุที่ถูกต้องสำหรับการประกาศใหม่ (และสำหรับการใช้งานในภายหลังหากสามารถเข้าถึงได้)
ค่าคงที่และชนิดของพอยเตอร์ว่าง
สำหรับส่วนนี้และส่วนนี้เท่านั้น ทุกครั้งที่มีการปรากฏของ " 0" จะหมายถึง "นิพจน์คงที่ซึ่งประเมินค่าได้เป็น0ซึ่งเป็นชนิดข้อมูล int" ในความเป็นจริง นิพจน์คงที่นั้นสามารถเป็นชนิดข้อมูลจำนวนเต็มใดก็ได้
นับตั้งแต่เริ่มมีการพัฒนาภาษา C ในปี 1972 ค่าคงที่ `constant` 0มีบทบาทสองอย่างคือ เป็นทั้งค่าคงที่จำนวนเต็มและ ค่า คงที่พอยเตอร์ว่างความกำกวมที่เกิดจากความหมายสองอย่างของ `constant` 0ถูกแก้ไขในภาษา C โดยใช้มาโครพรีโปรเซสเซอร์ `constant` NULLซึ่งโดยทั่วไปจะขยายเป็น `constant` ((void*)0)หรือ ` 0constant` ภาษา C++ ห้ามการแปลงโดยปริยายจาก `constant` void*ไปเป็นประเภทพอยเตอร์อื่น จึงทำให้ประโยชน์ของการแปลง0เป็น `constant` หายไป void*ผลที่ตามมาคือ `constant` เท่านั้น0ที่อนุญาตให้ใช้เป็นค่าคงที่พอยเตอร์ว่าง ซึ่งส่งผลเสียต่อการ โอเวอร์โหลดฟังก์ชัน
void foo ( char * ); void foo ( int );หากNULLมีการกำหนดเป็น0(ซึ่งมักจะเป็นเช่นนั้นใน C++) คำสั่งดังกล่าวfoo(NULL);จะเรียกใช้foo(int)ซึ่งเกือบจะแน่นอนว่าไม่ใช่สิ่งที่โปรแกรมเมอร์ตั้งใจ และไม่ใช่สิ่งที่การอ่านโค้ดอย่างผิวเผินบ่งบอก
C++11 แก้ไขปัญหานี้โดยการแนะนำคีย์เวิร์ดใหม่เพื่อทำหน้าที่เป็นค่าคงที่ของพอยเตอร์ว่างที่แตกต่างออกไป: nullptrมันมีประเภทเป็นnullptr_tซึ่งสามารถแปลงและเปรียบเทียบได้โดยปริยายกับประเภทพอยเตอร์ใดๆ หรือประเภทพอยเตอร์ไปยังสมาชิก แต่จะไม่สามารถแปลงหรือเปรียบเทียบได้โดยปริยายกับประเภทจำนวนเต็ม ยกเว้นboolในขณะที่ข้อเสนอเดิมระบุว่าค่า rvalue ที่มีประเภทเป็นnullptr_tไม่ควรแปลงเป็น ได้boolแต่กลุ่มทำงานภาษาหลักตัดสินใจว่าการแปลงดังกล่าวจะเป็นที่พึงปรารถนา เพื่อความสอดคล้องกับประเภทพอยเตอร์ทั่วไป การเปลี่ยนแปลงถ้อยคำที่เสนอได้รับการลงมติเห็นชอบเป็นเอกฉันท์ในเอกสารการทำงานในเดือนมิถุนายน พ.ศ. 2551 [1]ข้อเสนอที่คล้ายกันนี้ยังถูกนำเสนอต่อกลุ่มทำงานมาตรฐาน C และได้รับการยอมรับให้รวมอยู่ในC23 [ 17 ]
เพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า ค่า0คงที่พอยเตอร์ว่างนี้จึงยังคงใช้ได้อยู่
char * pc = nullptr ; // ถูกต้องint * pi = nullptr ; // ถูกต้องbool b = nullptr ; // ถูกต้อง b เป็นเท็จint i = nullptr ; // เกิดข้อผิดพลาดfoo ( nullptr ); // เรียก foo(nullptr_t) ไม่ใช่ foo(int); /* โปรดทราบว่า foo(nullptr_t) ในตัวอย่างข้างต้นจะเรียก foo(char*) โดยใช้การแปลงโดยปริยาย ก็ต่อเมื่อไม่มีฟังก์ชันอื่นที่โอเวอร์โหลดด้วยประเภทพอยเตอร์ที่เข้ากันได้ในขอบเขต หากมีการโอเวอร์โหลดหลายรายการ การแก้ไขจะล้มเหลวเนื่องจากมีความกำกวม เว้นแต่จะมีการประกาศ foo(nullptr_t) อย่างชัดเจน */ ในไฟล์เฮดเดอร์ประเภทมาตรฐานสำหรับ C++11 ประเภท nullptr_t ควรประกาศดังนี้: typedef decltype(nullptr) nullptr_t; แต่ไม่ใช่ดังนี้: typedef int nullptr_t; // เวอร์ชันก่อนหน้าของ C++ ที่กำหนดให้ NULL เป็น 0 typedef void* nullptr_t; // ANSI C ที่กำหนดให้ NULL เป็น ((void*)0) */การแจงนับแบบกำหนดประเภทอย่างเข้มงวด
ใน C++03 การแจงนับ (enumeration) ไม่มีความปลอดภัยทางประเภท (type-safe) กล่าวคือ มันมีค่าเท่ากับจำนวนเต็ม แม้ว่าประเภทของการแจงนับจะแตกต่างกันก็ตาม นี่ทำให้สามารถเปรียบเทียบค่าการแจงนับสองค่าที่มีประเภทต่างกันได้ ความปลอดภัยเพียงอย่างเดียวที่ C++03 มอบให้คือ จำนวนเต็มหรือค่าของประเภทการแจงนับหนึ่งจะไม่แปลงเป็นประเภทการแจงนับอื่นโดยอัตโนมัติ นอกจากนี้ ประเภทจำนวนเต็มพื้นฐานยังขึ้นอยู่กับการใช้งาน ดังนั้นโค้ดที่ขึ้นอยู่กับขนาดของการแจงนับจึงไม่สามารถใช้งานได้ในทุกแพลตฟอร์ม สุดท้าย ค่าการแจงนับมีขอบเขตจำกัดเฉพาะขอบเขตที่ครอบคลุมอยู่ ดังนั้นจึงเป็นไปไม่ได้ที่การแจงนับสองรายการที่แยกจากกันในขอบเขตเดียวกันจะมีชื่อสมาชิกที่ตรงกัน
C++11 อนุญาตให้มีการจัดประเภทพิเศษของการแจงนับซึ่งไม่มีปัญหาเหล่านี้ โดยแสดงออกผ่านการ ประกาศ enum class( enum structซึ่งก็ได้รับการยอมรับว่าเป็นคำพ้องความหมายเช่นกัน):
enum class CardSuit { CLUBS = 1 , HEARTS = 2 , DIAMONDS = 4 , SPADES = 8 };การแจงนับนี้มีความปลอดภัยทางประเภทข้อมูล ค่าของคลาส enum จะไม่ถูกแปลงเป็นจำนวนเต็มโดยอัตโนมัติ ดังนั้นจึงไม่สามารถนำไปเปรียบเทียบกับจำนวนเต็มได้ (นิพจน์ดังกล่าวCardSuit::SPADES == 8จะทำให้เกิดข้อผิดพลาดในการคอมไพล์)
ประเภทพื้นฐานของคลาส enum นั้นเป็นที่ทราบกันดีอยู่แล้ว โดยประเภทเริ่มต้นคือint; ซึ่งสามารถแทนที่ด้วยประเภทจำนวนเต็มอื่นได้ ดังที่เห็นได้ในตัวอย่างนี้:
enum class Direction : char { UP , DOWN , LEFT , RIGHT };ในการใช้ enum แบบเก่า ค่าต่างๆ จะอยู่ในขอบเขตภายนอก แต่ในการใช้ enum แบบใหม่ ค่าต่างๆ จะอยู่ภายในขอบเขตของชื่อคลาส enum ดังนั้นในตัวอย่างข้างต้น จึงUPเป็นค่าที่ไม่ถูกกำหนด แต่Direction::UPถูกกำหนดแล้ว
นอกจากนี้ยังมีไวยากรณ์ชั่วคราวเพื่ออนุญาตให้การแจงนับแบบเก่าสามารถระบุขอบเขตได้อย่างชัดเจน และกำหนดนิยามของประเภทพื้นฐานได้ด้วย:
enum Direction : char { UP , DOWN , LEFT , RIGHT };ในกรณีนี้ ชื่อของตัวแจงนับจะถูกกำหนดไว้ในขอบเขตของการแจงนับ ( Direction::UP) แต่เพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า จึงถูกวางไว้ในขอบเขตที่ครอบคลุมด้วย
ใน C++11 สามารถประกาศ enum ล่วงหน้าได้แล้ว ก่อนหน้านี้ ไม่สามารถประกาศ enum ล่วงหน้าได้ เนื่องจากขนาดของ enum ขึ้นอยู่กับนิยามของสมาชิก แต่ตราบใดที่ระบุขนาดของ enum ไม่ว่าจะโดยปริยายหรือโดยชัดแจ้ง ก็สามารถประกาศล่วงหน้าได้
enum Enum1 ; // ไม่ถูกต้องใน C++03 และ C++11; ไม่สามารถระบุชนิดข้อมูลพื้นฐานได้enum Enum2 : unsigned int ; // ถูกต้องใน C++11, ระบุชนิดข้อมูลพื้นฐานไว้อย่างชัดเจนenum class Enum3 ; // ถูกต้องใน C++11, ชนิดข้อมูลพื้นฐานคือ int enum class Enum4 : unsigned int ; // ถูกต้องใน C++11 enum Enum2 : unsigned short ; // ไม่ถูกต้องใน C++11 เนื่องจาก Enum2 เคยถูกประกาศด้วยชนิดข้อมูลพื้นฐานที่แตกต่างกันวงเล็บมุมฉาก
ตัวแยกวิเคราะห์ของ C++03 กำหนดให้ “ >>” เป็นตัวดำเนินการเลื่อนบิตไปทางขวาหรือตัวดำเนินการแยกสตรีมในทุกกรณี อย่างไรก็ตาม ในการประกาศเทมเพลตแบบซ้อนกัน โปรแกรมเมอร์มักละเลยการเว้นวรรคระหว่างวงเล็บมุมฉากสองอัน ทำให้เกิดข้อผิดพลาดทางไวยากรณ์ของคอมไพเลอร์
C++11 ปรับปรุงข้อกำหนดของตัวแยกวิเคราะห์เพื่อให้เครื่องหมายวงเล็บมุมฉากหลายตัวถูกตีความว่าเป็นการปิดรายการอาร์กิวเมนต์เทมเพลตในกรณีที่เหมาะสม สามารถแทนที่ได้โดยใช้วงเล็บครอบนิพจน์พารามิเตอร์โดยใช้ตัวดำเนินการไบนารี “ >”, “ >=” หรือ “ >>”
โดยใช้std :: vector ;template < bool Test > class SomeType ; vector < SomeType < 1 > 2 >> x1 ; // ตีความว่าเป็น std::vector ของ SomeType<true> // ตามด้วย "2 >> x1" ซึ่งไม่ใช่ไวยากรณ์ที่ถูกต้องสำหรับตัวประกาศ 1 คือ truevector < SomeType < ( 1 > 2 ) >> x1 ; // ตีความว่าเป็น std::vector ของ SomeType<false> // ตามด้วยตัวประกาศ "x1" ซึ่งเป็นไวยากรณ์ C++11 ที่ถูกต้อง (1>2) เป็นเท็จตัวดำเนินการแปลงแบบชัดเจน
C++98 เพิ่มexplicitคีย์เวิร์ด `true` เป็นตัวดัดแปลงในคอนสตรัคเตอร์เพื่อป้องกันไม่ให้คอนสตรัคเตอร์ที่มีอาร์กิวเมนต์เดียวถูกใช้เป็นตัวดำเนินการแปลงประเภทโดยปริยาย อย่างไรก็ตาม สิ่งนี้ไม่ได้ช่วยอะไรกับตัวดำเนินการแปลงประเภทจริง ๆ ตัวอย่างเช่น คลาสสมาร์ทพอยเตอร์อาจมี `true` operator bool()เพื่อให้ทำงานได้เหมือนพอยเตอร์แบบดั้งเดิมมากขึ้น: หากมีการแปลงนี้ ก็สามารถทดสอบได้ด้วยif (smart_ptr_variable)`true` (ซึ่งจะเป็นจริงถ้าพอยเตอร์ไม่ใช่ค่าว่าง และเป็นเท็จถ้าเป็นอย่างอื่น) อย่างไรก็ตาม สิ่งนี้ยังอนุญาตให้มีการแปลงประเภทอื่น ๆ ที่ไม่ได้ตั้งใจด้วย เนื่องจาก C++ boolถูกกำหนดให้เป็นประเภทเลขคณิต จึงสามารถแปลงเป็นประเภทจำนวนเต็มหรือแม้แต่ประเภทจุดลอยตัวได้โดยปริยาย ซึ่งอนุญาตให้มีการดำเนินการทางคณิตศาสตร์ที่ผู้ใช้ไม่ได้ตั้งใจ
ใน C++11 explicitคีย์เวิร์ด `boolean` สามารถใช้กับตัวดำเนินการแปลงค่าได้แล้ว เช่นเดียวกับตัวสร้าง คีย์เวิร์ดนี้จะป้องกันการใช้ฟังก์ชันแปลงค่าเหล่านั้นในการแปลงค่าโดยปริยาย อย่างไรก็ตาม บริบทของภาษาที่ต้องการค่าบูลีนโดยเฉพาะ (เงื่อนไขของคำสั่ง if และลูป และตัวถูกดำเนินการของตัวดำเนินการตรรกะ) จะนับเป็นการแปลงค่าโดยชัดแจ้ง และสามารถใช้ตัวดำเนินการแปลงค่าบูลีนได้
ตัวอย่างเช่น ฟีเจอร์นี้ช่วยแก้ปัญหาเรื่อง ค่าบูลีนที่ปลอดภัย ได้อย่างลงตัว
ชื่อแทนเทมเพลต
ใน C++03 สามารถกำหนด typedef ได้เฉพาะในฐานะคำพ้องความหมายสำหรับชนิดข้อมูลอื่นเท่านั้น รวมถึงคำพ้องความหมายสำหรับการกำหนดค่าเฉพาะของเทมเพลตโดยระบุอาร์กิวเมนต์เทมเพลตจริงทั้งหมด ไม่สามารถสร้างเทมเพลต typedef ได้ ตัวอย่างเช่น:
template < typename First , typename Second , int Third > class SomeType ;template < typename Second > typedef SomeType < OtherType , Second , 5 > TypedefName ; // ไม่ถูกต้องใน C++03ไฟล์นี้จะไม่สามารถคอมไพล์ได้
C++11 เพิ่มความสามารถนี้ด้วยไวยากรณ์ดังต่อไปนี้:
template < typename First , typename Second , int Third > class SomeType ;template < typename Second > using TypedefName = SomeType < OtherType , Second , 5 > ;ไวยากรณ์ นี้usingยังสามารถใช้เป็นการกำหนดชื่อแทนประเภท (type aliasing) ใน C++11 ได้อีกด้วย:
typedef void ( * FunctionType )( double ); // รูปแบบเก่าใช้FunctionType = void ( * )( double ); // ไวยากรณ์ใหม่ที่แนะนำสหภาพแรงงานที่ไม่จำกัด
ใน C++03 มีข้อจำกัดเกี่ยวกับประเภทของวัตถุที่สามารถเป็นสมาชิกของยูเนียนได้unionตัวอย่างเช่น ยูเนียนไม่สามารถมีวัตถุใดๆ ที่กำหนดคอนสตรัคเตอร์หรือดีสตรัคเตอร์ที่ไม่ใช่แบบธรรมดาได้ C++11 ยกเลิกข้อจำกัดเหล่านี้บางส่วน[2]
หากunionสมาชิกมีฟังก์ชันสมาชิกพิเศษที่ ไม่ใช่ฟังก์ชันพื้นฐาน คอมไพเลอร์จะไม่สร้างฟังก์ชันสมาชิกที่เทียบเท่าให้โดยunionอัตโนมัติ และจะต้องกำหนดฟังก์ชันนั้นด้วยตนเอง
นี่คือตัวอย่างง่ายๆ ของการใช้ยูเนียนที่อนุญาตใน C++11:
#include <new> // จำเป็นสำหรับการจัดวาง 'new'struct Point { int a ; int b ;จุด() {} จุด( int x , int y ) : x ( a ), y ( b ) {} };union MyUnion { int z ; double w ; Point p ; // ไม่ถูกต้องใน C++03; ถูกต้องใน C++11.// เนื่องจากมีสมาชิก Point จึงจำเป็นต้องมีการกำหนดคอนสตรัคเตอร์MyUnion () {}MyUnion ( const Point & pt ) : p ( pt ) {} // สร้างอ็อบเจ็กต์ Point โดยใช้ลิสต์เริ่มต้นMyUnion & operator = ( const Point & pt ) { // กำหนดวัตถุ Point โดยใช้การจัดวาง 'new' new ( & p ) Point ( pt ); return * this ; } };การเปลี่ยนแปลงนี้จะไม่ทำให้โค้ดที่มีอยู่เดิมเสียหาย เนื่องจากเป็นการผ่อนปรนกฎที่มีอยู่เท่านั้น
การปรับปรุงฟังก์ชันการทำงานของภาษาหลัก
คุณสมบัติเหล่านี้ทำให้ภาษาสามารถทำสิ่งต่างๆ ที่ก่อนหน้านี้เป็นไปไม่ได้ ยุ่งยากซับซ้อน หรือต้องใช้ไลบรารีที่ไม่สามารถใช้งานได้บนแพลตฟอร์มอื่น
แม่แบบวาริเอดิก
ใน C++11 เทมเพลตสามารถรับพารามิเตอร์เทมเพลตได้จำนวนแปรผัน นอกจากนี้ยังช่วยให้สามารถกำหนดฟังก์ชันแปรผัน ที่มีความปลอดภัยต่อชนิดข้อมูล ได้ อีกด้วย
สตริงลิเทอรัลใหม่
C++03 มีสตริงลิเทอรัล สองชนิด ชนิดแรกอยู่ภายในเครื่องหมายคำพูดคู่ จะสร้างอาร์เรย์ที่ลงท้ายด้วยค่าว่าง (null-terminated array const char) ชนิดที่สองกำหนดเป็น `<string>` L""จะสร้างอาร์เรย์ที่ลงท้ายด้วยค่าว่าง (null-terminated array) ชนิด `<string>` const wchar_tโดยที่ `<string> wchar_t` เป็นอักขระกว้าง (wide-character) ที่มีขนาดและความหมายไม่แน่นอน สตริงลิเทอรัลทั้งสองชนิดนี้ไม่รองรับสตริงลิเทอรัลที่มีการเข้ารหัส แบบ UTF-8 , UTF-16หรือUnicode ชนิดอื่น ๆ
C++11 รองรับการเข้ารหัส Unicode สามแบบ ได้แก่ UTF-8, UTF-16 และUTF-32นิยามของชนิดข้อมูลcharได้รับการแก้ไขเพื่อให้ระบุอย่างชัดเจนว่ามีขนาดอย่างน้อยเท่ากับขนาดที่จำเป็นในการจัดเก็บการเข้ารหัสแบบแปดบิตของ UTF-8 และมีขนาดใหญ่พอที่จะบรรจุสมาชิกใด ๆ ในชุดอักขระพื้นฐานสำหรับการทำงานของคอมไพเลอร์ ก่อนหน้านี้ในมาตรฐาน C++ เองกำหนดไว้เพียงขนาดหลังเท่านั้น จากนั้นจึงอาศัยมาตรฐาน C ในการรับประกันอย่างน้อย 8 บิต นอกจากนี้ C++11 ยังเพิ่มชนิดข้อมูลอักขระใหม่สองชนิด ได้แก่char16_tและchar32_tซึ่งออกแบบมาเพื่อจัดเก็บ UTF-16 และ UTF-32 ตามลำดับ
การสร้างสตริงลิเทอรัลสำหรับแต่ละการเข้ารหัสที่รองรับสามารถทำได้ดังนี้:
u8 "ฉันเป็นสตริง UTF-8" u "นี่คือสตริง UTF-16" u "นี่คือสตริง UTF-32"ชนิดของสตริงแรกคือแบบปกติconst char[]ชนิดของสตริงที่สองคือconst char16_t[](โปรดสังเกตคำนำหน้าด้วยตัวพิมพ์เล็ก 'u') ชนิดของสตริงที่สามคือconst char32_t[](คำนำหน้าด้วยตัวพิมพ์ใหญ่ 'U')
ในการสร้างสตริงลิเทอรัลแบบ Unicode มักจะเป็นประโยชน์ที่จะแทรกจุดรหัส Unicode ลงในสตริงโดยตรง C++11 อนุญาตให้ใช้ไวยากรณ์นี้:
u8 "นี่คืออักขระยูนิโค้ด: \u2018 " u "นี่คืออักขระยูนิโค้ดที่ใหญ่กว่า: \u2018 " U "นี่คืออักขระยูนิโค้ด: \U00002018 "ตัวเลขหลัง\uเครื่องหมายจุด (.) เป็นเลขฐานสิบหก ไม่จำเป็นต้องมี0xคำนำหน้า ตามปกติ \uตัวระบุนี้แทนจุดรหัสยูนิโค้ด 16 บิต หากต้องการป้อนจุดรหัส 32 บิต ให้ใช้เครื่องหมาย\Uจุด (.) และเลขฐานสิบหก 32 บิต เฉพาะจุดรหัสยูนิโค้ดที่ถูกต้องเท่านั้นที่สามารถป้อนได้ ตัวอย่างเช่น จุดรหัสในช่วง U+D800–U+DFFF นั้นไม่ได้รับอนุญาต เนื่องจากสงวนไว้สำหรับคู่ตัวอักษรแทน (surrogate pairs) ในการเข้ารหัส UTF-16
บางครั้งการหลีกเลี่ยงการใส่เครื่องหมายหลีกเลี่ยงให้กับสตริงด้วยตนเองก็มีประโยชน์ โดยเฉพาะอย่างยิ่งเมื่อใช้ค่าคงที่จาก ไฟล์ XMLภาษาสคริปต์ หรือนิพจน์ปกติ C++11 มีสตริงค่าคงที่แบบดิบให้ใช้งาน:
R"(ข้อมูลสตริง \ สิ่งของ " )" R"delimiter(The String Data \ Stuff " )delimiter"
ในกรณีแรก ทุกอย่างระหว่าง"(และ)"เป็นส่วนหนึ่งของสตริง อักขระ"และ\ไม่จำเป็นต้องมีการหลีกเลี่ยง ในกรณีที่สอง จะ"delimiter(เป็นจุดเริ่มต้นของสตริง และจะสิ้นสุดก็ต่อเมื่อ)delimiter"ถึง สตริงdelimiterสามารถเป็นสตริงใดก็ได้ที่มีความยาวไม่เกิน 16 ตัวอักษร รวมถึงสตริงว่าง สตริงนี้ต้องไม่มีช่องว่าง อักขระควบคุม(, )หรือ\อักขระ การใช้สตริงตัวคั่นนี้ ผู้ใช้สามารถมีลำดับ)"ภายในสตริงลิเทอรัลแบบดิบได้ ตัวอย่างเช่นR"delimiter("(a-z)")delimiter"เทียบเท่า"\"(a-z)\""กับ
สามารถใช้สตริงลิเทอรัลแบบดิบร่วมกับลิเทอรัลแบบไวด์หรือคำนำหน้าลิเทอรัลของยูนิโค้ดได้:
u8R"XXX(ฉันเป็นสตริง "UTF-8 ดิบ")XXX" uR"*(นี่คือสตริง "UTF-16 ดิบ")*" UR"(นี่คือสตริง "UTF-32 ดิบ")"
ตัวอักษรที่ผู้ใช้กำหนดเอง
C++03 มีตัวอักษรจำนวนหนึ่ง ตัวอักษรเหล่านี้12.5เป็นตัวอักษรที่คอมไพเลอร์จะแปลงเป็นชนิดข้อมูลที่doubleมีค่าเท่ากับ 12.5 อย่างไรก็ตาม การเพิ่มคำต่อท้ายfเช่น `characters` 12.5fจะสร้างค่าของชนิดข้อมูลfloatที่มีค่า 12.5 อยู่ภายใน คำต่อท้ายสำหรับตัวอักษรเหล่านี้ถูกกำหนดไว้ในข้อกำหนดของ C++ และโค้ด C++03 ไม่สามารถสร้างคำต่อท้ายตัวอักษรใหม่ได้
ในทางตรงกันข้าม C++11 อนุญาตให้ผู้ใช้กำหนดตัวแก้ไขค่าแบบใหม่ที่จะสร้างอ็อบเจ็กต์โดยอิงจากสตริงของอักขระที่ค่าดังกล่าวแก้ไข
การแปลงค่าตัวอักษรถูกกำหนดใหม่เป็นสองขั้นตอนที่แตกต่างกัน คือ ค่าตัวอักษรดิบและค่าตัวอักษรที่ผ่านการประมวลผล ค่าตัวอักษรดิบคือลำดับของอักขระที่มีชนิดข้อมูลเฉพาะ ในขณะที่ค่าตัวอักษรที่ผ่านการประมวลผลจะมีชนิดข้อมูลที่แตกต่างออกไป ค่าตัวอักษร C++ 1234ในรูปแบบค่าตัวอักษรดิบคือลำดับของอักขระ'1', '2', '3', ส่วน '4'ในรูปแบบค่าตัวอักษรที่ผ่านการประมวลผลแล้ว จะเป็นจำนวนเต็ม 1234 ค่าตัวอักษร C++ 0xAในรูปแบบดิบคือ'0', 'x', 'A', ในขณะที่ในรูปแบบที่ผ่านการประมวลผลแล้วจะเป็นจำนวนเต็ม 10
ตัวอักษรสามารถขยายได้ทั้งในรูปแบบดิบและรูปแบบที่ผ่านการประมวลผล ยกเว้นตัวอักษรสตริง ซึ่งสามารถประมวลผลได้เฉพาะในรูปแบบที่ผ่านการประมวลผลเท่านั้น ข้อยกเว้นนี้เกิดจากข้อเท็จจริงที่ว่าสตริงมีคำนำหน้าที่มีผลต่อความหมายและประเภทเฉพาะของอักขระนั้นๆ
ตัวอักษรที่ผู้ใช้กำหนดทั้งหมดเป็นคำต่อท้าย ไม่สามารถกำหนดตัวอักษรนำหน้าได้ คำต่อท้ายทั้งหมดที่ขึ้นต้นด้วยอักขระใดๆ ยกเว้นเครื่องหมายขีดล่าง ( _) จะถูกสงวนไว้ตามมาตรฐาน ดังนั้น ตัวอักษรที่ผู้ใช้กำหนดทั้งหมดต้องมีคำต่อท้ายที่ขึ้นต้นด้วยเครื่องหมายขีดล่าง ( _) [ 18 ]
ตัวอักษรที่ผู้ใช้กำหนดเองซึ่งประมวลผลรูปแบบดิบของตัวอักษรนั้นถูกกำหนดผ่านตัวดำเนินการตัวอักษร ซึ่งเขียนว่าoperator""ตัวอย่างต่อไปนี้:
ตัวดำเนินการOutputType "" _mysuffix ( const char * literal_string ) { // สมมติว่า OutputType มีคอนสตรัคเตอร์ที่รับ const char* OutputType ret ( literal_string ); return ret ; }OutputType some_variable = 1234 _mysuffix ; // สมมติว่า OutputType มีเมธอด get_value() ที่ส่งค่า double กลับมาassert ( some_variable . get_value () == 1234.0 )คำสั่งกำหนดค่าOutputType some_variable = 1234_mysuffix;จะเรียกใช้โค้ดที่กำหนดโดยฟังก์ชันตัวอักษรที่ผู้ใช้กำหนดเอง ฟังก์ชันนี้ถูกส่งผ่าน"1234"เป็นสตริงแบบ C ดังนั้นจึงมีตัวจบสตริงเป็นค่าว่าง (null terminator)
กลไกทางเลือกสำหรับการประมวลผลค่าตัวเลขจำนวนเต็มและจุดลอยตัวแบบดิบๆ คือ การใช้เทมเพลตแบบแปรผันได้ (variadic template) :
template < char ... > OutputType operator "" _tuffix ();OutputType some_variable = 1234 _tuffix ; OutputType another_variable = 2.17 _tuffix ;นี่เป็นการสร้างอินสแตนซ์ของฟังก์ชันประมวลผลค่าคงที่ใน รูปแบบนี้ operator""_tuffix<'1', '2', '3', '4'>()ในรูปแบบนี้ จะไม่มีอักขระ null ปิดท้ายสตริง จุดประสงค์หลักของการทำเช่นนี้คือการใช้constexprคีย์เวิร์ด `const` ของ C++11 เพื่อให้แน่ใจว่าคอมไพเลอร์จะแปลงค่าคงที่ทั้งหมดในระหว่างการคอมไพล์ โดยสมมติว่าOutputType`const` เป็นประเภทที่สร้างได้ด้วย `const` และคัดลอกได้ และฟังก์ชันประมวลผลค่าคงที่เป็นconstexprฟังก์ชัน
สำหรับค่าตัวเลขคงที่ ประเภทของค่าคงที่ที่ผ่านการประมวลผลแล้วจะunsigned long longเป็นค่าคงที่จำนวนเต็มหรือlong doubleค่าคงที่จำนวนทศนิยม (หมายเหตุ: ไม่จำเป็นต้องใช้ประเภทจำนวนเต็มที่มีเครื่องหมาย เนื่องจากค่าคงที่ที่มีเครื่องหมายนำหน้าจะถูกแยกวิเคราะห์เป็นนิพจน์ที่มีเครื่องหมายเป็นตัวดำเนินการนำหน้าเอกภาคและจำนวนที่ไม่มีเครื่องหมาย) ไม่มีรูปแบบแม่แบบทางเลือกอื่น:
ตัวดำเนินการOutputType "" _suffix ( unsigned long long ); ตัวดำเนินการOutputType "" _suffix ( long double );OutputType some_variable = 1234 _suffix ; // ใช้โอเวอร์โหลด 'unsigned long long' OutputType another_variable = 3.1416 _suffix ; // ใช้โอเวอร์โหลด 'long double'ตามคำนำหน้าสตริงใหม่ที่กล่าวถึงก่อนหน้านี้ สำหรับสตริงลิเทอรัล จะใช้คำนำหน้าเหล่านี้:
ตัวดำเนินการOutputType "" _ssuffix ( const char * string_values , size_t num_chars ); ตัวดำเนินการOutputType "" _ssuffix ( const wchar_t * string_values , size_t num_chars ); ตัวดำเนินการ OutputType "" _ssuffix ( const char16_t * string_values , size_t num_chars ); ตัวดำเนินการOutputType "" _ssuffix ( const char32_t * string_values , size_t num_chars );OutputType some_variable = "1234" _ssuffix ; // ใช้โอเวอร์โหลด 'const char*' OutputType some_variable = u8 "1234" _ssuffix ; // ใช้โอเวอร์โหลด 'const char*' OutputType some_variable = L "1234" _ssuffix ; // ใช้โอเวอร์โหลด 'const wchar_t*' OutputType some_variable = u "1234" _ssuffix ; // ใช้โอเวอร์โหลด 'const char16_t*' OutputType some_variable = U "1234" _ssuffix ; // ใช้โอเวอร์โหลด 'const char32_t*'ไม่มีรูปแบบแม่แบบอื่น การกำหนดตัวอักษรแบบคงที่ก็คล้ายคลึงกัน
โมเดลหน่วยความจำแบบมัลติเธรด
C++11 เป็นมาตรฐานที่รองรับการเขียนโปรแกรมแบบมัลติเธรด
ประกอบด้วยสองส่วนหลัก ได้แก่ โมเดลหน่วยความจำที่อนุญาตให้เธรดหลายตัวทำงานร่วมกันในโปรแกรม และไลบรารีที่รองรับการโต้ตอบระหว่างเธรด (ดูส่วนเกี่ยวกับฟังก์ชันการทำงานของเธรด ในบทความนี้ )
แบบจำลองหน่วยความจำกำหนดว่าเมื่อใดที่เธรดหลายเธรดสามารถเข้าถึงตำแหน่งหน่วยความจำเดียวกันได้ และระบุว่าเมื่อใดที่การอัปเดตโดยเธรดหนึ่งจะปรากฏให้เธรดอื่นเห็น
พื้นที่จัดเก็บข้อมูลเฉพาะเธรด
ในสภาพแวดล้อมแบบมัลติเธรด เป็นเรื่องปกติที่แต่ละเธรดจะมีตัวแปร ที่ไม่ซ้ำกัน ซึ่งเกิดขึ้นแล้วสำหรับตัวแปรโลคอลของฟังก์ชัน แต่จะไม่เกิดขึ้นสำหรับตัวแปรโกลบอลและตัวแปรสแตติก
ระยะเวลา การจัดเก็บข้อมูลเฉพาะเธรดแบบใหม่(นอกเหนือจากแบบคงที่ แบบไดนามิกและแบบอัตโนมัติ ที่มีอยู่แล้ว ) จะระบุโดยตัวระบุการจัดเก็บthread_localข้อมูล
วัตถุใดๆ ที่สามารถมีระยะเวลาการจัดเก็บแบบคงที่ (เช่น อายุการใช้งานตลอดการทำงานของโปรแกรม) สามารถกำหนดให้มีระยะเวลาเฉพาะเธรดแทนได้ จุดประสงค์ก็คือ เช่นเดียวกับตัวแปรที่มีระยะเวลาคงที่อื่นๆ วัตถุเฉพาะเธรดสามารถเริ่มต้นใช้งานได้โดยใช้คอนสตรัคเตอร์และทำลายได้โดยใช้ดีสตรัคเตอร์
ฟังก์ชันสมาชิกพิเศษที่มีการกำหนดค่าเริ่มต้นอย่างชัดเจน
ใน C++03 คอมไพเลอร์จะจัดเตรียมคอนสตรัคเตอร์เริ่มต้น คอนสตรัคเตอร์สำเนา ตัวดำเนินการกำหนดค่าสำเนา (COD operator=) และดีสตรัคเตอร์ สำหรับคลาสที่ไม่ได้จัดเตรียมไว้ให้เอง โปรแกรมเมอร์สามารถแทนที่ค่าเริ่มต้นเหล่านี้ได้โดยการกำหนดเวอร์ชันที่กำหนดเอง นอกจากนี้ C++ ยังกำหนดตัวดำเนินการส่วนกลางหลายตัว (เช่น COD operator new) ที่ใช้งานได้กับทุกคลาส ซึ่งโปรแกรมเมอร์สามารถแทนที่ได้
อย่างไรก็ตาม การควบคุมการสร้างค่าเริ่มต้นเหล่านี้ค่อนข้างจำกัด ตัวอย่างเช่น การทำให้คลาสไม่สามารถคัดลอกได้โดยเนื้อแท้ อาจทำได้โดยการประกาศคอนสตรัคเตอร์สำเนาส่วนตัวและตัวดำเนินการกำหนดค่าสำเนา และไม่กำหนดฟังก์ชันเหล่านั้น การพยายามใช้ฟังก์ชันเหล่านี้ถือเป็นการละเมิดกฎOne Definition Rule (ODR) แม้ว่าจะไม่จำเป็นต้องมีข้อความวินิจฉัย[ 19 ]การละเมิดอาจส่งผลให้เกิดข้อผิดพลาดของลิงเกอร์
ในกรณีของคอนสตรัคเตอร์เริ่มต้น คอมไพเลอร์จะไม่สร้างคอนสตรัคเตอร์เริ่มต้นหากคลาสถูกกำหนดด้วย คอนสตรัคเตอร์ ใดๆสิ่งนี้มีประโยชน์ในหลายกรณี แต่การมีทั้งคอนสตรัคเตอร์เฉพาะและคอนสตรัคเตอร์เริ่มต้นที่คอมไพเลอร์สร้างขึ้นก็มีประโยชน์เช่นกัน
C++11 อนุญาตให้กำหนดค่าเริ่มต้นและลบฟังก์ชันสมาชิกพิเศษเหล่านี้ได้อย่างชัดเจน[ 20 ]ตัวอย่างเช่น คลาสนี้ประกาศอย่างชัดเจนว่าสามารถใช้คอนสตรัคเตอร์เริ่มต้นได้:
คลาสSomeType { SomeType () = default ; // คอนสตรัคเตอร์เริ่มต้นถูกระบุไว้อย่างชัดเจนSomeType ( OtherType value ); };ฟังก์ชันที่ถูกลบอย่างชัดเจน
สามารถปิดใช้งานฟังก์ชันได้อย่างชัดเจน ซึ่งมีประโยชน์ในการป้องกันการแปลงประเภทโดย= deleteปริยาย สามารถใช้ตัวระบุเพื่อห้ามการเรียกใช้ฟังก์ชันด้วยประเภทพารามิเตอร์เฉพาะ[ 20 ]ตัวอย่างเช่น:
void noInt ( double i ); void noInt ( int ) = delete ;การพยายามเรียก ใช้ฟังก์ชัน noInt()โดยมีintพารามิเตอร์จะถูกปฏิเสธโดยคอมไพเลอร์ แทนที่จะทำการแปลงแบบเงียบๆ เป็นค่าอื่นdoubleการเรียกใช้ฟังก์ชันnoInt()โดยไม่มีพารามิเตอร์floatยังคงใช้งานได้
สามารถห้ามการเรียกใช้ฟังก์ชันด้วยประเภทอื่นนอกเหนือdoubleจากการใช้เทมเพลตได้:
double onlyDouble ( double d ) { return d ; }template < typename T > double onlyDouble ( T ) = delete ;การเรียกใช้ฟังก์ชันonlyDouble(1.0)จะทำงานได้ ในขณะที่ การ onlyDouble(1.0f)เรียกใช้ฟังก์ชันอื่นจะทำให้เกิดข้อผิดพลาดในการคอมไพล์
ฟังก์ชันสมาชิกและคอนสตรัคเตอร์ของคลาสสามารถลบได้เช่นกัน ตัวอย่างเช่น สามารถป้องกันการคัดลอกอ็อบเจ็กต์ของคลาสได้โดยการลบคอนสตรัคเตอร์สำหรับการคัดลอกและoperator =:
คลาสNonCopyable { NonCopyable (); NonCopyable ( const NonCopyable & ) = delete ; NonCopyable & operator = ( const NonCopyable & ) = delete ; };พิมพ์long long int
ใน C++03 ชนิดข้อมูลจำนวนเต็มที่ใหญ่ที่สุดคือlong intซึ่งรับประกันว่าจะมีจำนวนบิตที่ใช้งานได้อย่างน้อยเท่ากับintส่งผลให้long intมีขนาด 64 บิตในการใช้งานที่เป็นที่นิยมบางอย่างและ 32 บิตในการใช้งานอื่นๆ C++11 เพิ่มชนิดข้อมูลจำนวนเต็มใหม่long long intเพื่อแก้ไขปัญหานี้ ซึ่งรับประกันว่าจะมีขนาดใหญ่อย่างน้อยเท่ากับlong intและมีขนาดไม่น้อยกว่า 64 บิต ชนิดข้อมูลนี้ได้รับการแนะนำครั้งแรกโดยC99ให้กับมาตรฐาน C และคอมไพเลอร์ C++ ส่วนใหญ่รองรับเป็นส่วนขยายอยู่แล้ว[ 21 ] [ 22 ]
การยืนยันแบบคงที่
C++03 มีวิธีการทดสอบการยืนยัน สองวิธี ได้แก่ มาโครassertและคำสั่งพรีโปรเซสเซอร์#errorอย่างไรก็ตาม ทั้งสองวิธีไม่เหมาะสมสำหรับการใช้งานในเทมเพลต: มาโครจะทดสอบการยืนยันในระหว่างการประมวลผล ในขณะที่คำสั่งพรีโปรเซสเซอร์จะทดสอบการยืนยันในระหว่างการประมวลผลล่วงหน้า ซึ่งเกิดขึ้นก่อนการสร้างอินสแตนซ์ของเทมเพลต นอกจากนี้ ทั้งสองวิธีไม่เหมาะสมสำหรับการทดสอบคุณสมบัติที่ขึ้นอยู่กับพารามิเตอร์ของเทมเพลต
ยูทิลิตี้ใหม่นี้แนะนำวิธีการใหม่ในการทดสอบการยืนยันในระหว่างการคอมไพล์ โดยใช้คีย์เวิร์ดใหม่static_assertการประกาศจะมีรูปแบบดังนี้:
static_assert( constant-expression , error-message );
ต่อไปนี้เป็นตัวอย่างวิธีstatic_assertการใช้งาน:
static_assert (( GREEKPI > 3.14 ) && ( GREEKPI < 3.15 ), "GREEKPI ไม่ถูกต้อง!" );template < class T > struct Check { static_assert ( sizeof ( int ) <= sizeof ( T ), "T ไม่ใหญ่พอ!" ); };โดยใช้std :: is_integral ;template < class Integral > Integral foo ( Integral x , Integral y ) { static_assert ( is_integral < Integral >:: value , "พารามิเตอร์ของฟังก์ชัน foo() ต้องเป็นชนิดข้อมูลจำนวนเต็ม" ); }เมื่อนิพจน์คงที่คือfalseคอมไพเลอร์จะสร้างข้อความแสดงข้อผิดพลาด ตัวอย่างแรกคล้ายกับคำสั่งพรีโปรเซสเซอร์#errorแม้ว่าพรีโปรเซสเซอร์จะรองรับเฉพาะประเภทจำนวนเต็มเท่านั้น[ 23 ]ในทางตรงกันข้าม ในตัวอย่างที่สอง การยืนยันจะถูกตรวจสอบทุกครั้งที่มีการสร้างอินสแตนซ์ของคลาสCheckเทมเพลต
การยืนยันแบบคงที่นั้นมีประโยชน์นอกเหนือจากเทมเพลตด้วยเช่นกัน ตัวอย่างเช่น การใช้งานอัลกอริทึมบางอย่างอาจขึ้นอยู่กับขนาดของlong longตัวแปร a ที่ใหญ่กว่าตัวแปร b intซึ่งเป็นสิ่งที่มาตรฐานไม่ได้รับประกัน สมมติฐานดังกล่าวใช้ได้กับระบบและคอมไพเลอร์ส่วนใหญ่ แต่ไม่ใช่ทั้งหมด
อนุญาตsizeofให้ทำงานกับสมาชิกของคลาสโดยไม่ต้องระบุอ็อบเจ็กต์อย่างชัดเจน
ใน C++03 sizeofตัวดำเนินการสามารถใช้กับชนิดข้อมูลและอ็อบเจ็กต์ได้ แต่ไม่สามารถใช้เพื่อทำสิ่งนี้ได้:
struct SomeType { OtherType member ; };sizeof ( SomeType :: member ); // ใช้ไม่ได้กับ C++03 ใช้ได้กับ C++11คำสั่งนี้ควรส่งคืนขนาดของOtherType. C++03 ไม่อนุญาตให้ทำเช่นนี้ ดังนั้นจึงเป็นข้อผิดพลาดในการคอมไพล์ C++11 อนุญาตให้ทำเช่นนี้ได้ และยังอนุญาตให้ใช้กับalignofตัวดำเนินการที่แนะนำใน C++11 ด้วย
การจัดตำแหน่งวัตถุควบคุมและสอบถาม
C++11 อนุญาตให้สอบถามและควบคุมการจัดเรียงตัวแปรด้วย alignofและalignas
ตัวalignofดำเนินการนี้รับชนิดข้อมูลและส่งคืนค่าขอบเขตไบต์ที่เป็นกำลังสองของ 2 ซึ่งอินสแตนซ์ของชนิดข้อมูลนั้นจะต้องถูกจัดสรร (เป็นstd::size_t) เมื่อได้รับชนิดข้อมูลอ้างอิงalignofจะคืนค่าการจัดเรียงของชนิดข้อมูลที่ถูกอ้างอิง สำหรับอาร์เรย์ จะคืนค่าการจัดเรียงของชนิดข้อมูลองค์ประกอบ
ตัวalignasระบุจะควบคุมการจัดเรียงหน่วยความจำสำหรับตัวแปร ตัวระบุรับค่าคงที่หรือชนิดข้อมูล เมื่อระบุชนิดข้อมูลalignas(T)จะเป็นตัวย่อalignas(alignof(T))สำหรับ ตัวอย่างเช่น เพื่อระบุว่าอาร์เรย์อักขระควรได้รับการจัดเรียงอย่างเหมาะสมเพื่อเก็บค่าทศลอย:
alignas ( float ) unsigned char c [ sizeof ( float )]อนุญาตให้ใช้งานการจัดการขยะอัตโนมัติ
มาตรฐาน C++ รุ่นก่อนหน้านี้ได้กำหนดให้โปรแกรมเมอร์สามารถจัดการการเก็บขยะได้ผ่านทาง `get_garbage_name` set_new_handlerแต่ไม่ได้ให้คำจำกัดความของความสามารถในการเข้าถึงอ็อบเจ็กต์เพื่อวัตถุประสงค์ในการเก็บขยะอัตโนมัติ C++11 กำหนดเงื่อนไขที่ค่าของพอยเตอร์จะ "ได้มาอย่างปลอดภัย" จากค่าอื่นๆ การใช้งานอาจระบุว่าทำงานภายใต้ความปลอดภัยของพอยเตอร์อย่างเข้มงวดซึ่งในกรณีนี้ พอยเตอร์ที่ไม่ได้รับมาตามกฎเหล่านี้อาจกลายเป็นไม่ถูกต้อง
คุณลักษณะ
C++11 มีไวยากรณ์มาตรฐานสำหรับส่วนขยายของคอมไพเลอร์/เครื่องมือต่างๆ โดยปกติแล้วส่วนขยายเหล่านี้จะระบุโดยใช้#pragmaคำสั่งหรือคำหลักเฉพาะของผู้ผลิต (เช่น__attribute__สำหรับ GNU และ__declspecMicrosoft) ด้วยไวยากรณ์ใหม่นี้ ข้อมูลเพิ่มเติมสามารถระบุได้ในรูปแบบของแอตทริบิวต์ที่ล้อมรอบด้วยวงเล็บเหลี่ยมสองชั้น แอตทริบิวต์นี้สามารถนำไปใช้กับองค์ประกอบต่างๆ ของซอร์สโค้ดได้:
int [[ attr1 ]] i [[ attr2 , attr3 ]];[[ attr4 ( arg1 , arg2 )]] ถ้า( cond ) { [[ vendor :: attr5 ]] คืนค่าi ; }ในตัวอย่างข้างต้น แอตทริบิวต์ใช้กับประเภทของตัวแปรและใช้กับตัวแปรนั้นเองใช้กับคำสั่ง และ ใช้กับคำสั่งส่งคืน โดยทั่วไป (แต่มีข้อยกเว้นบางประการ) แอตทริบิวต์ที่ระบุสำหรับเอนทิตี ที่มีชื่อจะอยู่หลังชื่อ และอยู่ก่อนเอนทิตีในกรณีอื่นๆ ดังที่แสดงไว้ข้างต้น อาจมีการระบุแอตทริบิวต์หลายรายการไว้ในวงเล็บเหลี่ยมคู่เดียว อาจมีการระบุอาร์กิวเมนต์เพิ่มเติมสำหรับแอตทริบิวต์ และแอตทริบิวต์อาจถูกกำหนดขอบเขตโดยเนมสเปซแอตทริบิวต์เฉพาะของผู้จำหน่าย [[attr1]]i[[attr2]][[attr3]][[attr4]]if[[vendor::attr5]]
แนะนำว่าแอตทริบิวต์ไม่ควรมีความหมายเชิงภาษา และไม่ควรเปลี่ยนแปลงความหมายของโปรแกรมเมื่อถูกละเลย แอตทริบิวต์อาจมีประโยชน์ในการให้ข้อมูล เช่น ช่วยให้คอมไพเลอร์สามารถวินิจฉัยปัญหาได้ดีขึ้น หรือปรับปรุงโค้ดที่สร้างขึ้นให้เหมาะสมยิ่งขึ้น
C++11 มีแอตทริบิวต์มาตรฐานสองอย่าง ได้แก่การระบุว่าฟังก์ชันไม่ส่งค่าคืน และการช่วยเพิ่มประสิทธิภาพโค้ดแบบมัลติเธรดโดยการระบุว่าอาร์กิวเมนต์ของฟังก์ชันหรือค่าที่ส่งคืนนั้นมีการพึ่งพาซึ่งกันและกัน [[noreturn]][[carries_dependency]]
การเปลี่ยนแปลงไลบรารีมาตรฐาน C++
มีการเพิ่มคุณสมบัติใหม่หลายอย่างในไลบรารีมาตรฐาน C++11 คุณสมบัติเหล่านี้หลายอย่างสามารถนำไปใช้ได้ภายใต้มาตรฐานเดิม แต่บางส่วนต้องอาศัยคุณสมบัติหลักใหม่ของ C++11 (ไม่มากก็น้อย)
ส่วนใหญ่ของไลบรารี ใหม่ ได้รับการกำหนดไว้ในเอกสารC++ Standards Committee's Library Technical Report (เรียกว่า TR1) ซึ่งเผยแพร่ในปี 2548 ปัจจุบันมีการใช้งาน TR1 แบบเต็มรูปแบบและบางส่วนหลายรูปแบบโดยใช้เนมสเปซstd::tr1สำหรับ C++11 นั้น ไลบรารีเหล่านี้ถูกย้ายไปยังเนมสเปซstdอย่างไรก็ตาม เมื่อคุณสมบัติของ TR1 ถูกนำมาใช้ในไลบรารีมาตรฐาน C++11 พวกมันก็ได้รับการปรับปรุงให้เหมาะสมด้วยคุณสมบัติของภาษา C++11 ที่ไม่มีอยู่ในเวอร์ชัน TR1 เริ่มต้น นอกจากนี้ พวกมันอาจได้รับการปรับปรุงด้วยคุณสมบัติที่สามารถทำได้ภายใต้ C++03 แต่ไม่ได้เป็นส่วนหนึ่งของข้อกำหนด TR1 ดั้งเดิม
การอัปเกรดส่วนประกอบไลบรารีมาตรฐาน
C++11 นำเสนอคุณสมบัติภาษาใหม่ ๆ มากมายที่ส่วนประกอบไลบรารีมาตรฐานที่มีอยู่เดิมสามารถใช้ประโยชน์ได้ ตัวอย่างเช่น คอนเทนเนอร์ส่วนใหญ่ในไลบรารีมาตรฐานสามารถได้รับประโยชน์จากการรองรับตัวสร้างแบบย้าย (move constructor) ที่ใช้การอ้างอิงแบบ Rvalue ทั้งสำหรับการย้ายคอนเทนเนอร์ขนาดใหญ่ไปมาอย่างรวดเร็ว และสำหรับการย้ายเนื้อหาของคอนเทนเนอร์เหล่านั้นไปยังตำแหน่งหน่วยความจำใหม่ ส่วนประกอบไลบรารีมาตรฐานได้รับการอัปเกรดด้วยคุณสมบัติภาษา C++11 ใหม่ตามความเหมาะสม ซึ่งรวมถึงแต่ไม่จำกัดเพียง:
- การอ้างอิงค่า R และการรองรับการย้ายที่เกี่ยวข้อง
- รองรับหน่วยการเข้ารหัส UTF-16 และหน่วยการเข้ารหัส UTF-32 รวมถึงประเภทอักขระยูนิโค้ด
- เทมเพลตแบบแปรผัน (ควบคู่กับการอ้างอิงค่า R เพื่อให้สามารถส่งต่อได้อย่างสมบูรณ์แบบ)
- นิพจน์ค่าคงที่ในขั้นตอนการคอมไพล์
decltypeexplicitตัวดำเนินการแปลง- ฟังก์ชันที่ประกาศค่าเริ่มต้นหรือถูกลบ
นอกจากนี้ เวลาผ่านไปนานมากนับตั้งแต่มาตรฐาน C++ รุ่นก่อนหน้า มีการเขียนโค้ดจำนวนมากโดยใช้ไลบรารีมาตรฐาน ซึ่งได้เผยให้เห็นส่วนต่างๆ ของไลบรารีมาตรฐานที่ควรได้รับการปรับปรุง หนึ่งในหลายๆ ด้านที่ได้รับการพิจารณาปรับปรุงคือตัวจัดสรรหน่วยความจำของไลบรารีมาตรฐาน C++11 ได้เพิ่มโมเดลตัวจัดสรรหน่วยความจำแบบใหม่ที่อิงตามขอบเขต เพื่อเสริมโมเดลเดิม
สิ่งอำนวยความสะดวกในการร้อยด้าย
แม้ว่าภาษา C++03 จะมีโมเดลหน่วยความจำที่รองรับการทำงานแบบมัลติเธรด แต่การสนับสนุนหลักสำหรับการใช้งานมัลติเธรดอย่างแท้จริงนั้นมาจากไลบรารีมาตรฐานของ C++11
มี คลาสเธรด ( std::thread) ให้ใช้งาน ซึ่งรับอ็อบเจ็กต์ฟังก์ชัน (และชุดอาร์กิวเมนต์เสริมที่จะส่งผ่านไปยังฟังก์ชันนั้น) เพื่อเรียกใช้ในเธรดใหม่ สามารถทำให้เธรดหยุดทำงานจนกว่าเธรดอื่นที่กำลังทำงานอยู่จะเสร็จสิ้น โดยให้การสนับสนุนการเข้าร่วมเธรดผ่านstd::thread::join()ฟังก์ชันสมาชิก การเข้าถึงอ็อบเจ็กต์เธรดดั้งเดิมที่อยู่เบื้องหลังสำหรับ การดำเนินการเฉพาะ แพลตฟอร์ม นั้นมีให้ใช้งานได้ในกรณีที่ทำได้ ผ่านstd::thread::native_handle()ฟังก์ชันสมาชิก
เพื่อการซิงโครไนซ์ระหว่างเธรด ได้ มีการเพิ่ม มิวเท็กซ์ ที่เหมาะสม ( std::mutex, std::recursive_mutex, เป็นต้น) และตัวแปรเงื่อนไข ( std::condition_variableและstd::condition_variable_any) เข้าไปในไลบรารี ซึ่งสามารถเข้าถึงได้ผ่านทางล็อก Resource Acquisition Is Initialization (RAII) ( std::lock_guardและstd::unique_lock) และอัลกอริธึมการล็อกเพื่อให้ใช้งานได้ง่าย
สำหรับการทำงานระดับต่ำที่มีประสิทธิภาพสูง บางครั้งจำเป็นต้องมีการสื่อสารระหว่างเธรดโดยไม่ต้องมีภาระเพิ่มเติมจากมิวเท็กซ์ ซึ่งทำได้โดยใช้การดำเนินการแบบอะตอมิก บนตำแหน่งหน่วยความจำ โดยสามารถระบุข้อจำกัดการมองเห็นหน่วยความจำขั้นต่ำที่จำเป็นสำหรับการดำเนินการได้ นอกจากนี้ ยังสามารถใช้ ตัว กั้นหน่วยความจำแบบชัดเจนเพื่อจุดประสงค์นี้ได้เช่นกัน
ไลบรารีเธรด C++11 ยังรวมถึงฟิวเจอร์และโพรมีสสำหรับส่งผลลัพธ์แบบอะซิงโครนัสระหว่างเธรด และstd::packaged_taskสำหรับการห่อหุ้มการเรียกฟังก์ชันที่สามารถสร้างผลลัพธ์แบบอะซิงโครนัสดังกล่าวได้ ข้อเสนอฟิวเจอร์ถูกวิพากษ์วิจารณ์เนื่องจากขาดวิธีการรวมฟิวเจอร์และตรวจสอบความสมบูรณ์ของโพรมีสหนึ่งภายในชุดของโพรมีส[ 24 ]
คุณสมบัติการทำงานแบบมัลติเธรดระดับสูงเพิ่มเติม เช่นพูลเธรดได้ถูกส่งต่อไปยังรายงานทางเทคนิค C++ ในอนาคต คุณสมบัติเหล่านี้ไม่ได้เป็นส่วนหนึ่งของ C++11 แต่คาดว่าการใช้งานในอนาคตจะสร้างขึ้นบนพื้นฐานของคุณสมบัติไลบรารีเธรดทั้งหมด
ฟังก์ชัน ใหม่นี้std::asyncช่วยให้สามารถเรียกใช้งานและเชื่อมโยงงานต่างๆ ได้อย่างสะดวกstd::futureผู้ใช้สามารถเลือกได้ว่าจะให้เรียกใช้งานแบบอะซิงโครนัสบนเธรดแยกต่างหาก หรือแบบซิงโครนัสบนเธรดที่รอค่า โดยค่าเริ่มต้น การใช้งานจะเลือกแบบอะซิงโครนัส ซึ่งเป็นวิธีที่ง่ายในการใช้ประโยชน์จากการทำงานพร้อมกันของฮาร์ดแวร์โดยไม่เกิดการโอเวอร์ซับซิบชัน และให้ข้อดีบางประการของพูลเธรดสำหรับการใช้งานแบบง่ายๆ
ประเภททูเปิล
ทูเพิล (Tuple)คือกลุ่มของวัตถุที่แตกต่างกันซึ่งมีมิติที่กำหนดไว้ล่วงหน้า อาจกล่าวได้ว่าทูเพิลเป็นการขยายแนวคิดของตัวแปรสมาชิกในโครงสร้าง (struct)
ทูเพิลชนิด TR1 ในเวอร์ชัน C++11 ได้รับประโยชน์จากคุณสมบัติของ C++11 เช่นเทมเพลตแบบแปรผัน (variadic templates ) ในการใช้งานอย่างเหมาะสม เวอร์ชัน TR1 จำเป็นต้องมีจำนวนชนิดข้อมูลสูงสุดที่กำหนดโดยการใช้งาน และต้องใช้เทคนิคมาโครจำนวนมาก ในทางตรงกันข้าม การใช้งานเวอร์ชัน C++11 ไม่จำเป็นต้องมีจำนวนชนิดข้อมูลสูงสุดที่กำหนดโดยการใช้งานอย่างชัดเจน แม้ว่าคอมไพเลอร์จะมีค่าความลึกของการเรียกซ้ำสูงสุดภายในสำหรับการสร้างอินสแตนซ์ของเทมเพลต (ซึ่งเป็นเรื่องปกติ) แต่ทูเพิลเวอร์ชัน C++11 จะไม่เปิดเผยค่านี้ให้ผู้ใช้ทราบ
เมื่อใช้เทมเพลตแบบแปรผันการประกาศคลาสทูเพิลจะมีลักษณะดังนี้:
namespace std { template < class ... Types > class tuple ; }ตัวอย่างการนิยามและการใช้งานของชนิดข้อมูลทูเพิล:
typedef std :: tuple < int , double , long & , const char *> TestTuple ; long lengthy = 12 ; TestTuple proof ( 18 , 6.5 , lengthy , "Ciao!" );lengthy = std :: get <0> ( proof ); // กำหนดค่า 18 ให้กับ 'lengthy' std :: get <3> ( proof ) = "Beautiful!" ; // แก้ไของค์ประกอบที่ สี่ของทูเปิลสามารถสร้างทูเพิลได้proofโดยไม่ต้องกำหนดเนื้อหา แต่เฉพาะในกรณีที่ชนิดขององค์ประกอบในทูเพิลมีคอนสตรัคเตอร์เริ่มต้นเท่านั้น นอกจากนี้ ยังสามารถกำหนดค่าทูเพิลให้กับทูเพิลอื่นได้ หากชนิดของทูเพิลทั้งสองเหมือนกัน ชนิดขององค์ประกอบแต่ละตัวจะต้องมีคอนสตรัคเตอร์สำหรับการคัดลอก มิฉะนั้น ชนิดขององค์ประกอบแต่ละตัวในทูเพิลด้านขวาจะต้องสามารถแปลงเป็นชนิดขององค์ประกอบที่สอดคล้องกันในทูเพิลด้านซ้ายได้ หรือชนิดขององค์ประกอบที่สอดคล้องกันในทูเพิลด้านซ้ายจะต้องมีคอนสตรัคเตอร์ที่เหมาะสม
typedef std :: tuple < int , double , string > Tuple1 t1 ; typedef std :: tuple < char , short , const char *> Tuple2 t2 ( 'X' , 2 , "Hola!" ); t1 = t2 ; // โอเค สององค์ประกอบแรกสามารถแปลงได้// ส่วนองค์ประกอบที่สามสามารถสร้างได้จาก 'const char *'เช่นเดียวกับstd::make_pairสำหรับstd::pairมีฟังก์ชันstd::make_tupleที่สร้างstd::tuples โดยอัตโนมัติโดยใช้การอนุมานประเภทและautoช่วยในการประกาศทูเปิลดังกล่าวstd::tieสร้างทูเปิลของการอ้างอิง lvalue เพื่อช่วยในการแยกทูเปิล ฟังก์ชันstd::ignoreก็ช่วยในส่วนนี้เช่นกัน ดูตัวอย่าง:
#include <string> #include <tuple>โดยใช้std :: string ;auto record = std :: make_tuple ( "Hari Ram" , "New Delhi" , 3.5 , 'A' ); string name ; float gpa ; char grade ; std :: tie ( name , std :: ignore , gpa , grade ) = record ; // std::ignore ช่วยตัดชื่อสถานที่ออกstd :: cout << name << ' ' << gpa << ' ' << grade << std :: endl ;สามารถใช้ตัวดำเนินการเชิงสัมพันธ์ได้ (ระหว่างทูเปิลที่มีจำนวนองค์ประกอบเท่ากัน) และมีนิพจน์สองแบบให้ใช้ตรวจสอบคุณลักษณะของทูเปิล (เฉพาะระหว่างการคอมไพล์):
std::tuple_size<T>::valueส่งคืนจำนวนองค์ประกอบในทูเปิTลstd::tuple_element<I, T>::typeส่งคืนประเภทของหมายเลขวัตถุIในทูเปิTล
ตารางแฮช
การรวมตารางแฮช (คอนเทนเนอร์แบบเชื่อมโยงที่ไม่เรียงลำดับ) เข้าไว้ในไลบรารีมาตรฐานของ C++ เป็นหนึ่งในข้อเรียกร้องที่เกิดขึ้นบ่อยที่สุด แต่ไม่ได้นำมาใช้ใน C++03 เนื่องจากข้อจำกัดด้านเวลาเท่านั้น ถึงแม้ว่าตารางแฮชจะมีประสิทธิภาพน้อยกว่าต้นไม้สมดุลในกรณีที่เลวร้ายที่สุด (ในกรณีที่มีการชนกันจำนวนมาก) แต่ก็ทำงานได้ดีกว่าในแอปพลิเคชันจริงหลายๆ อย่าง
การชนกันของค่าจะถูกจัดการผ่านการเชื่อมโยงเชิงเส้น เท่านั้น เนื่องจากคณะกรรมการเห็นว่าไม่เหมาะสมที่จะกำหนดมาตรฐานวิธีการระบุแอดเดรสแบบเปิดซึ่งก่อให้เกิดปัญหาภายในมากมาย (โดยเฉพาะอย่างยิ่งเมื่อยอมรับการลบองค์ประกอบ) เพื่อหลีกเลี่ยงการชนกันของชื่อกับไลบรารีที่ไม่เป็นมาตรฐานซึ่งพัฒนาการใช้งานตารางแฮชของตนเอง จึงใช้คำนำหน้า "unordered" แทน "hash"
ไลบรารีใหม่นี้มีตารางแฮชสี่ประเภท ซึ่งแตกต่างกันตรงที่ยอมรับองค์ประกอบที่มีคีย์เดียวกันหรือไม่ (คีย์ที่ไม่ซ้ำกันหรือคีย์ที่เทียบเท่า) และว่าแต่ละคีย์เชื่อมโยงกับค่าที่เกี่ยวข้องหรือไม่ ตารางแฮชเหล่านี้สอดคล้องกับคอนเทนเนอร์แบบเชื่อมโยงที่ใช้ต้นไม้ค้นหาแบบไบนารี ที่มีอยู่สี่แบบ โดยมี คำนำหน้าว่า unordered_
| ประเภทของตารางแฮช | ค่าที่เกี่ยวข้อง | คีย์ที่เทียบเท่า |
|---|---|---|
std::unordered_set | เลขที่ | เลขที่ |
std::unordered_multiset | เลขที่ | ใช่ |
std::unordered_map | ใช่ | เลขที่ |
std::unordered_multimap | ใช่ | ใช่ |
คลาสใหม่เหล่านี้ตรงตามข้อกำหนดทั้งหมดของคลาสคอนเทนเนอร์และมีเมธอดทั้งหมดที่จำเป็นในการเข้าถึงองค์ประกอบต่างๆ ได้แก่insert, erase, begin, end.
ฟีเจอร์ใหม่นี้ไม่จำเป็นต้องมีการขยายแกนหลักของภาษา C++ ใดๆ (แม้ว่าการใช้งานจะใช้ประโยชน์จากคุณสมบัติต่างๆ ของภาษา C++11 ก็ตาม) เพียงแค่มีการขยายส่วนหัวเล็กน้อยและ<functional>การเพิ่มส่วนหัว เพิ่มเติมเท่านั้น ไม่จำเป็นต้องเปลี่ยนแปลงคลาสมาตรฐานที่มีอยู่เดิม และไม่ขึ้นอยู่กับการขยายอื่นๆ ของไลบรารีมาตรฐาน <unordered_set><unordered_map>
std::arrayและstd::forward_list
นอกจากตารางแฮชแล้ว ยังมีการเพิ่มคอนเทนเนอร์อีกสองประเภทลงในไลบรารีมาตรฐาน ประเภทแรกstd::arrayคือ คอนเทนเนอร์ขนาดคงที่ ซึ่งมีประสิทธิภาพมากกว่าstd::vectorแต่ปลอดภัยกว่าและใช้งานง่ายกว่าอาร์เรย์แบบ C ประเภทที่สองstd::forward_listคือ รายการเชื่อมโยงเดี่ยว ซึ่งให้พื้นที่จัดเก็บข้อมูลที่มีประสิทธิภาพมากกว่ารายการเชื่อมโยงคู่std::listเมื่อไม่จำเป็นต้องมีการวนซ้ำแบบสองทิศทาง
นิพจน์ปกติ
ไลบรารีใหม่ ซึ่งกำหนดไว้ในไฟล์เฮดเดอร์ใหม่<regex>ประกอบด้วยคลาสใหม่สองสามคลาส:
- นิพจน์ปกติจะถูกแทนด้วยอินสแตนซ์ของคลาส
std::regexเทมเพลต - เหตุการณ์ต่างๆ จะถูกแทนด้วยอินสแตนซ์ของคลาส
std::match_resultsเทมเพลต std::regex_iteratorใช้สำหรับวนซ้ำเพื่อตรวจสอบการจับคู่ทั้งหมดของนิพจน์ปกติ (regex)
ฟังก์ชันนี้std::regex_searchใช้สำหรับการค้นหา ในขณะที่สำหรับ 'ค้นหาและแทนที่' std::regex_replaceจะใช้ฟังก์ชันที่ส่งคืนสตริงใหม่[ 25 ]
ต่อไปนี้เป็นตัวอย่างการใช้งานstd::regex_iterator:
#รวม<regex>using std :: cregex_iterator ; using std :: regex ; using std :: string ;const char * pattern = R " ( [^ ,.\t\n]+ ) " ; // ค้นหาคำที่คั่นด้วยช่องว่าง คอมมา จุด แท็บ และขึ้นบรรทัดใหม่regex re ( pattern ); // โยนข้อยกเว้นหากรูปแบบไม่ถูกต้องconst char * target = "Unseen University - Ankh-Morpork" ;// ใช้ regex_iterator เพื่อระบุคำทั้งหมดของ 'target' ที่คั่นด้วยอักขระของ 'pattern' for ( cregex_iterator iter ( target , target + strlen ( target ), re ); iter != re . end (); ++ iter ) { string match_str = iter -> str (); std :: cout << match_str << std :: endl ; }ไลบรารีนี้<regex>ไม่จำเป็นต้องแก้ไขส่วนหัวที่มีอยู่เดิม (แม้ว่าจะใช้ส่วนหัวเหล่านั้นในกรณีที่เหมาะสม) หรือขยายภาษาหลัก ใน ภาษา C ตาม มาตรฐาน POSIXนิพจน์ปกติก็สามารถใช้งานได้ผ่านทาง<regex.h>เช่น กัน
สมาร์ทพอยเตอร์อเนกประสงค์
C++11 มีคุณสมบัติstd::unique_ptrและปรับปรุงstd::shared_ptrจากstd::weak_ptrTR1 ( std::auto_ptrส่วนนี้ถูกยกเลิกการใช้งานแล้ว)
ระบบสร้างเลขสุ่มที่ขยายได้
ไลบรารีมาตรฐานของภาษา C มีความสามารถในการสร้างเลขสุ่มเทียมผ่านฟังก์ชันrand`create_random_numbers` อย่างไรก็ตาม อัลกอริทึมนั้นถูกมอบหมายให้ผู้พัฒนาไลบรารีเป็นผู้ดูแลทั้งหมด ภาษา C++ ได้รับฟังก์ชันนี้มาโดยไม่มีการเปลี่ยนแปลงใดๆ แต่ C++11 ได้เพิ่มวิธีการใหม่ในการสร้างเลขสุ่มเทียม
ฟังก์ชันการสร้างเลขสุ่มใน C++11 แบ่งออกเป็นสองส่วน: ส่วนแรกคือกลไกการสร้างเลขสุ่ม ซึ่งประกอบด้วยสถานะของตัวสร้างเลขสุ่มและสร้างเลขสุ่มเทียม และส่วนที่สองคือการกระจาย ซึ่งกำหนดช่วงและลักษณะการกระจายทางคณิตศาสตร์ของผลลัพธ์ ทั้งสองส่วนนี้รวมกันเพื่อสร้างเป็นอ็อบเจ็กต์ตัวสร้างเลขสุ่ม
แตกต่างจากมาตรฐาน C randกลไกของ C++11 จะมาพร้อมกับอัลกอริธึมตัวสร้างพื้นฐานสามแบบ:
นอกจากนี้ C++11 ยังมีการแจกจ่ายมาตรฐานอีกหลายแบบ:
uniform_int_distribution,uniform_real_distribution,bernoulli_distribution,binomial_distribution,geometric_distribution,negative_binomial_distribution,poisson_distribution,exponential_distribution,gamma_distribution,weibull_distribution,extreme_value_distribution,normal_distribution,lognormal_distribution,chi_squared_distribution,cauchy_distribution,fisher_f_distribution,student_t_distribution,discrete_distribution,piecewise_constant_distributionและpiecewise_linear_distribution.
ตัวสร้างและการแจกแจงจะรวมกันดังตัวอย่างนี้:
#include <functional> #include <random>using std :: mt19937 ; using std :: uniform_int_distribution ;uniform_int_distribution < int > distribution ( 0 , 99 ); mt19937 engine ; // Mersenne twister MT19937 auto generator = std :: bind ( distribution , engine ); int random = generator (); // สร้างค่าสุ่มแบบจำนวนเต็มสม่ำเสมอระหว่าง 0 ถึง 99 int random2 = distribution ( engine ); // สร้างค่าสุ่มอีกค่าหนึ่งโดยตรงโดยใช้ distribution และ engine objectsการอ้างอิง Wrapper
การ อ้างอิง แบบ Wrapperได้รับมาจากอินสแตนซ์ของเทมเพลตคลาสreference_wrapperการอ้างอิงแบบ Wrapper คล้ายกับการอ้างอิงปกติ (' &') ในภาษา C++ ในการรับการอ้างอิงแบบ Wrapper จากวัตถุใดๆrefจะใช้เทมเพลตฟังก์ชัน (สำหรับการอ้างอิงค่าคงที่crefจะใช้การอ้างอิงแบบปกติ)
การอ้างอิงแบบ Wrapper มีประโยชน์อย่างยิ่งสำหรับเทมเพลตฟังก์ชัน ซึ่งจำเป็นต้องอ้างอิงถึงพารามิเตอร์แทนที่จะเป็นการคัดลอก:
#include <functional> #include <iostream>// ฟังก์ชันนี้จะรับค่าอ้างอิงของพารามิเตอร์ 'r' และเพิ่มค่าขึ้นหนึ่งvoid func ( int & r ) { r ++ ; }// ฟังก์ชันเทมเพลตtemplate < class F , class P > void g ( F f , P t ) { f ( t ); }int main () { int i = 0 ; g ( func , i ); // 'g<void(int& r), int>' ถูกสร้างขึ้น// ดังนั้น 'i' จะไม่ถูกแก้ไขstd :: cout << i << std :: endl ; // แสดงผล -> 0g ( func , std :: ref ( i )); // 'g<void(int& r), reference_wrapper<int>>' ถูกสร้างขึ้น// จากนั้น 'i' จะถูกแก้ไขstd :: cout << i << std :: endl ; // ผลลัพธ์ -> 1 }ยูทิลิตี้ใหม่นี้ถูกเพิ่มเข้าไปใน<functional>ไฟล์เฮดเดอร์ที่มีอยู่แล้ว และไม่จำเป็นต้องมีการขยายเพิ่มเติมใดๆ ในภาษา C++
ตัวห่อโพลีมอร์ฟิกสำหรับวัตถุฟังก์ชัน
ตัวห่อแบบโพลีมอร์ฟิกสำหรับออบเจ็กต์ฟังก์ชันนั้นคล้ายคลึงกับตัวชี้ฟังก์ชันในด้านความหมายและไวยากรณ์ แต่มีความผูกพันน้อยกว่าและสามารถอ้างอิงถึงสิ่งใดก็ได้ที่สามารถเรียกได้ (ตัวชี้ฟังก์ชัน ตัวชี้ฟังก์ชันสมาชิก หรือฟังก์ชันเตอร์) ที่มีอาร์กิวเมนต์เข้ากันได้กับอาร์กิวเมนต์ของตัวห่อ
ตัวอย่างจะช่วยให้เข้าใจลักษณะเฉพาะได้ชัดเจนยิ่งขึ้น:
#รวม<ฟังก์ชันการทำงาน>using std :: function ; using std :: plus ;// การสร้าง Wrapper โดยใช้คลาสเทมเพลต 'function' function < int ( int , int ) > func ;plus < int > add ; // 'plus' ถูกประกาศเป็น 'template <class T> T plus(T, T) ;' // จากนั้น 'add' จะเป็นประเภท 'int add( int x, int y )'func = add ; // ถูกต้อง - พารามิเตอร์และประเภทค่าส่งคืนเหมือนกันint a = func ( 1 , 2 ); // หมายเหตุ: หาก wrapper 'func' ไม่ได้อ้างอิงถึงฟังก์ชันใดๆ// จะมีการโยนข้อยกเว้น 'std::bad_function_call'ฟังก์ชัน< bool ( short , short ) > func2 ; ถ้า( ! func2 ) { // เป็นจริงเพราะ 'func2' ยังไม่ได้รับฟังก์ชันbool adjacent ( long x , long y ); func2 = & adjacent ; // OK - พารามิเตอร์และประเภทการส่งคืนสามารถแปลงได้struct Test { bool operator ()( short x , short y ); };ทดสอบรถ; // 'std::ref' เป็นฟังก์ชันเทมเพลตที่ส่งคืนตัวห่อ// ของฟังก์ชันสมาชิก 'operator()' ของโครงสร้าง 'car' func = std :: ref ( car ); } func = func2 ; // ตกลง - พารามิเตอร์และประเภทการส่งคืนสามารถแปลงได้คลาสเทมเพลตfunctionถูกกำหนดไว้ภายในไฟล์เฮดเดอร์<functional>โดยไม่จำเป็นต้องแก้ไขภาษา C++ แต่อย่างใด
ลักษณะการพิมพ์สำหรับการเขียนโปรแกรมเมตา
เมตาโปรแกรมมิงคือการสร้างโปรแกรมที่สร้างหรือแก้ไขโปรแกรมอื่น (หรือตัวมันเอง) ซึ่งสามารถเกิดขึ้นได้ระหว่างการคอมไพล์หรือระหว่างการทำงานคณะกรรมการมาตรฐาน C++ได้ตัดสินใจที่จะนำเสนอไลบรารีสำหรับเมตาโปรแกรมมิงระหว่างการคอมไพล์ผ่านเทมเพลต
ต่อไปนี้เป็นตัวอย่างของเมตาโปรแกรมที่ใช้มาตรฐาน C++03: การเรียกซ้ำของอินสแตนซ์เทมเพลตสำหรับการคำนวณเลขชี้กำลังจำนวนเต็ม:
template < int B , int N > struct Power { // การเรียกซ้ำและการรวมกันenum { VALUE = B * Power < B , N - 1 >:: VALUE }; };template < int B > struct Power < B , 0 > { // เงื่อนไขการสิ้นสุด ''N == 0'' enum { VALUE = 1 }; };int quartic_of_three = Power < 3 , 4 >:: VALUE ;อัลกอริทึมหลายตัวสามารถทำงานกับข้อมูลประเภทต่างๆ ได้ เทมเพลตของ C++ สนับสนุนการเขียนโปรแกรมแบบเจเนริกและทำให้โค้ดกระชับและมีประโยชน์มากขึ้น อย่างไรก็ตาม เป็นเรื่องปกติที่อัลกอริทึมจะต้องการข้อมูลเกี่ยวกับประเภทข้อมูลที่ใช้ ข้อมูลนี้สามารถดึงออกมาได้ในระหว่างการสร้างอินสแตนซ์ของคลาสเทมเพลตโดยใช้คุณสมบัติของประเภท (type traits )
คุณลักษณะของประเภทสามารถระบุหมวดหมู่ของวัตถุและลักษณะทั้งหมดของคลาส (หรือของโครงสร้าง) ได้ โดยมีการกำหนดไว้ในไฟล์เฮดเดอร์<type_traits>ใหม่
ในตัวอย่างถัดไป มีฟังก์ชันเทมเพลต 'elaborate' ซึ่งขึ้นอยู่กับประเภทข้อมูลที่กำหนด จะสร้างอินสแตนซ์ของอัลกอริทึมที่เสนอหนึ่งในสองแบบ ( Algorithm::perform)
#include <type_traits>using std :: is_floating_point ; using std :: is_integral ;// วิธีการดำเนินการแบบแรกtemplate < bool B > struct Algorithm { template < class T1 , class T2 > static int perform ( T1 & , T2 & ) { // ... } };// วิธีการทำงานแบบที่สองtemplate <> struct Algorithm < true > { template < class T1 , class T2 > static int perform ( T1 , T2 ) { // ... } };// การสร้างอินสแตนซ์ของ 'elaborate' จะสร้างอินสแตนซ์วิธีการดำเนินการที่ถูกต้องโดยอัตโนมัติtemplate < class T1 , class T2 > int elaborate ( T1 A , T2 B ) { // ใช้เฉพาะวิธีที่สองเมื่อ 'T1' เป็นจำนวนเต็มและ 'T2' เป็นเลขทศนิยม มิฉะนั้นให้ใช้วิธีแรกreturn Algorithm < is_integral < T1 >:: value && is_floating_point < T2 >:: value >:: perform ( A , B ) ; }ผ่านคุณลักษณะของประเภทที่กำหนดไว้ในส่วนหัว<type_traits>ยังสามารถสร้างการดำเนินการแปลงประเภทได้ ( static_castและ การดำเนินการดัง const_castกล่าวไม่เพียงพอภายในเทมเพลต)
การเขียนโปรแกรมประเภทนี้สร้างโค้ดที่สวยงามและกระชับ อย่างไรก็ตาม จุดอ่อนของเทคนิคเหล่านี้คือการแก้ไขข้อผิดพลาด: มันยุ่งยากในระหว่างการคอมไพล์และยากมากในระหว่างการทำงานของโปรแกรม
วิธีการที่สม่ำเสมอในการคำนวณประเภทค่าส่งคืนของอ็อบเจ็กต์ฟังก์ชัน
การกำหนดประเภทค่าส่งคืนของอ็อบเจ็กต์ฟังก์ชันเทมเพลตในระหว่างการคอมไพล์นั้นไม่ใช่เรื่องง่าย โดยเฉพาะอย่างยิ่งหากค่าส่งคืนขึ้นอยู่กับพารามิเตอร์ของฟังก์ชัน ตัวอย่างเช่น:
struct Clear { // ประเภทของพารามิเตอร์เท่ากับประเภทของค่าที่ส่งคืนint operator ()( int ) const ; double operator ()( double ) const ; };template < class Obj > class Calculus { private : Obj member ; public : template < class Arg > Arg operator ()( Arg & a ) const { return member ( a ); } };เมื่อสร้างอินสแตนซ์ของเทมเพลตคลาสCalculus<Clear>ฟังก์ชันอ็อบเจ็กต์ของ คลาสนั้น calculusจะมีประเภทการส่งคืนเหมือนกับฟังก์ชันอ็อบเจ็กต์ของคลาสอื่นเสมอClearอย่างไรก็ตาม สำหรับคลาสที่กำหนดConfusedด้านล่างนี้:
struct Confused { // ประเภทของพารามิเตอร์ไม่เท่ากับประเภทของค่าที่ส่งคืนdouble operator ()( int ) const ; int operator ()( double ) const ; };การพยายามสร้างอินสแตนซ์Calculus<Confused>จะทำให้ประเภทการส่งคืนของCalculusไม่เหมือนกับของคลาสConfusedคอมไพเลอร์อาจสร้างคำเตือนเกี่ยวกับการแปลงจากintเป็นdoubleและในทางกลับกัน
TR1 นำเสนอ และ C++11 นำมาใช้ ซึ่งคลาสเทมเพลตstd::result_ofช่วยให้สามารถกำหนดและใช้ประเภทการส่งคืนของอ็อบเจ็กต์ฟังก์ชันสำหรับการประกาศทุกครั้งได้ โดยอ็อบเจ็กต์นี้ จะ CalculusVer2ใช้std::result_ofอ็อบเจ็กต์อื่นในการกำหนดประเภทการส่งคืนของอ็อบเจ็กต์ฟังก์ชัน:
#include <type_traits>โดยใช้std :: result_of ;template < class Obj > class CalculusVer2 { private : Obj member ; public : template < class Arg > typename result_of < Obj ( Arg ) >:: type operator ()( Arg & a ) const { return member ( a ); } };ด้วยวิธีนี้ ในกรณีของอ็อบเจ็กต์ฟังก์ชันCalculusVer2<Confused>จะไม่มีการแปลง คำเตือน หรือข้อผิดพลาดใดๆ
การเปลี่ยนแปลงเพียงอย่างเดียวจากเวอร์ชัน TR1 คือstd::result_ofเวอร์ชัน TR1 อนุญาตให้การใช้งานล้มเหลวในการกำหนดประเภทผลลัพธ์ของการเรียกฟังก์ชันได้ เนื่องจากการเปลี่ยนแปลงใน C++ เพื่อรองรับdecltypeเวอร์ชัน C++11 จึงstd::result_ofไม่จำเป็นต้องมีกรณีพิเศษเหล่านี้อีกต่อไป การใช้งานจำเป็นต้องคำนวณประเภทในทุกกรณี
ปรับปรุงความเข้ากันได้กับภาษา C
เพื่อให้เข้ากันได้กับCจาก C99 ได้มีการเพิ่มสิ่งเหล่านี้: [ 26 ]
- ตัวประมวลผลล่วงหน้า: [ 27 ]
- มาโครแบบแปรผัน
- การเชื่อมต่อสตริงข้อความแคบ/กว้างที่อยู่ติดกัน
_Pragma()– เทียบเท่ากับ#pragma.
long long– ชนิดข้อมูลจำนวนเต็มที่มีความยาวอย่างน้อย 64 บิต__func__– มาโครที่ประเมินค่าเป็นชื่อของฟังก์ชันที่มันอยู่ภายใน- ส่วนหัว:
cstdbool(stdbool.h),cstdint(stdint.h),cinttypes(inttypes.h)
คุณสมบัติที่วางแผนไว้แต่เดิมแต่ถูกตัดออกหรือไม่ได้รวมไว้
ไปเขียนรายงานแยกต่างหากกันเลย:
เลื่อนออกไป:
- แนวคิด
- การสนับสนุนการเก็บขยะที่สมบูรณ์หรือจำเป็นมากขึ้น
- การสะท้อนความคิด
- ขอบเขตมาโคร
คุณสมบัติที่ถูกลบออกหรือเลิกใช้งานแล้ว
คำว่าจุดลำดับถูกลบออก โดยแทนที่ด้วยการระบุว่าการดำเนินการหนึ่งรายการมีลำดับก่อนการดำเนินการอื่น หรือการดำเนินการสองรายการไม่มีลำดับ[ 29 ]
การใช้คีย์เวิร์ดexportพร้อมกับเทมเพลตที่ส่งออกถูกลบออก[ 30 ]คีย์เวิร์ดexportยังคงสงวนไว้ และต่อมาได้นำกลับมาใช้ใหม่ในC++20พร้อม กับการเพิ่มโมดูล
ข้อกำหนดข้อยกเว้นแบบไดนามิกไม่แนะนำให้ใช้แล้ว[ 30 ]การกำหนดฟังก์ชันที่ไม่โยนข้อยกเว้นในเวลาคอมไพล์สามารถทำได้ด้วยnoexceptคำหลัก ซึ่งมีประโยชน์สำหรับการเพิ่มประสิทธิภาพ
std::auto_ptrเลิกใช้แล้ว เนื่องจากถูกแทนที่ด้วยstd::unique_ptr.
คลาสพื้นฐานของอ็อบเจ็กต์ฟังก์ชัน ( std::unary_function, std::binary_function), อะแดปเตอร์สำหรับพอยเตอร์ไปยังฟังก์ชัน และอะแดปเตอร์สำหรับพอยเตอร์ไปยังสมาชิก รวมถึงคลาสตัวผูก (binder classes) ล้วนถูกยกเลิกการใช้งานแล้ว
ภาคผนวก D.2 ระบุว่า: "การใช้registerคำหลักเป็นตัวระบุคลาสการจัดเก็บข้อมูล (§7.1.1) นั้นไม่แนะนำให้ใช้แล้ว"
ดูเพิ่มเติม
ลิงก์ภายนอก
- คณะกรรมการมาตรฐาน C++
- C++0X: โฉมใหม่ของมาตรฐาน C++
- บทความในบล็อกของ Herb Sutter เกี่ยวกับ C++11
- บทความในบล็อกของ Anthony Williams เกี่ยวกับ C++11
- การบรรยายเรื่อง C++0x โดย Bjarne Stroustrup ที่มหาวิทยาลัย Waterloo บันทึกไว้เมื่อวันที่ 23 มกราคม 2009 ในWayback Machine
- สถานการณ์ของภาษา: บทสัมภาษณ์กับ บียาร์เน สโตรสทรุป (15 สิงหาคม 2551) เก็บถาวรเมื่อวันที่ 31 มกราคม 2552 ที่Wayback Machine
- หน้าวิกิที่จะช่วยติดตามคุณสมบัติหลักของภาษา C++ 0x และความพร้อมใช้งานในคอมไพเลอร์
- เอกสารอ้างอิงไลบรารีมาตรฐาน C++11 ออนไลน์
- คอมไพเลอร์ C++11 ออนไลน์
- คำถามที่พบบ่อยเกี่ยวกับ C++11ของ Bjarne Stroustrup
- ข้อมูลเพิ่มเติมเกี่ยวกับคุณสมบัติของ C++11: ลูป for แบบอิงตามช่วง, เหตุผลที่ auto_ptr ถูกยกเลิกการใช้งาน ฯลฯ