การแยกวิเคราะห์ที่น่าหงุดหงิดที่สุด
การแยกวิเคราะห์ที่ยุ่งยากที่สุดคือการแก้ไขความกำกวม ที่ไม่เป็นไปตามสัญชาตญาณ ในC++ในบางสถานการณ์ ไวยากรณ์ของ C++ ไม่สามารถแยกแยะระหว่างการเริ่มต้นพารามิเตอร์ของวัตถุการประกาศวัตถุ หรือการประกาศฟังก์ชันในขณะที่ระบุประเภทการส่งคืนของฟังก์ชันได้ [ 1 ] ในสถานการณ์เหล่านี้ คอมไพเลอร์จำเป็นต้องตีความคำสั่งเป็นอย่างหลัง แม้ว่านี่จะไม่ใช่เจตนาของโปรแกรมเมอร์ก็ตาม
ปัญหาเกิดจาก ข้อจำกัด ด้านความเข้ากันได้แบบย้อนหลังที่กำหนดโดยความจำเป็นที่ C++ ต้องเป็นซูเปอร์เซ็ตของC C ไม่มีแนวคิดเกี่ยวกับการสร้างอ็อบเจ็กต์ ดังนั้นจึงจะตีความโค้ดเป็นการประกาศฟังก์ชันเสมอ C++ ได้นำไวยากรณ์สำหรับการสร้างอ็อบเจ็กต์มาใช้ ซึ่งในบางกรณีอาจไปตรงกับการประกาศประเภทฟังก์ชันโดยไม่ได้ตั้งใจ
คำนี้ถูกใช้ครั้งแรกโดยScott MeyersในหนังสือEffective STLของ เขาในปี 2001 [ 2 ]มันเป็นปัญหาทั่วไปสำหรับ C++ เวอร์ชันก่อนC++11ซึ่งแนะนำไวยากรณ์ทางเลือกที่เรียกว่าการเริ่มต้นแบบสม่ำเสมอซึ่งใช้วงเล็บปีกกา{}แทนวงเล็บเหลี่ยม()เพื่อหลีกเลี่ยงความกำกวมทางไวยากรณ์[ 3 ]
ตัวอย่าง
หล่อแบบซี
ตัวอย่างง่ายๆ จะปรากฏขึ้นเมื่อต้องการแปลงประเภทข้อมูลโดยใช้ฟังก์ชันเพื่อแปลงนิพจน์สำหรับการกำหนดค่าเริ่มต้นให้กับตัวแปร:
void f ( double x ) { // กำหนดค่าแบบ C-style-cast ให้กับจำนวนเต็มชื่อ i int i ( int ( x )); }บรรทัดนี้กำกวม การตีความที่เป็นไปได้ประการหนึ่งคือการประกาศตัวแปรที่มีค่าเริ่มต้นที่ได้จากการแปลงเป็นค่าหนึ่งอย่างไรก็ตาม ภาษา C อนุญาตให้มีวงเล็บเกินความจำเป็นรอบ การประกาศ พารามิเตอร์ของฟังก์ชันในกรณีนี้ การประกาศตัวแปร จึงเป็นการประกาศฟังก์ชันที่เทียบเท่ากับสิ่งต่อไปนี้:inti(int(x));ixinti
// ฟังก์ชันชื่อ i รับค่าจำนวนเต็มและส่งคืนค่าจำนวนเต็มint i ( int x );ชั่วคราวไม่ระบุชื่อ
ตัวอย่างที่ละเอียดกว่านี้ได้แก่:
คลาสTimer { private : // ฟิลด์... public : // คอนสตรัคเตอร์ ฯลฯint getTime () { // คืนค่าเวลาเป็นจำนวนเต็ม} };คลาสTimeKeeper { private : Timer timer ; public : explicit TimeKeeper ( Timer timer ) : timer { std :: move ( timer )} {}int getTime () { return timer.getTime ( ) ; } };int main () { TimeKeeper keeper ( Timer ()); return keeper . getTime (); }ประโยคนี้กำกวม สามารถตีความได้สองแบบ คือTimeKeeperkeeper(Timer());
- นิยามตัวแปรสำหรับตัวแปร
keeperของคลาสTimeKeeperซึ่งเริ่มต้นด้วยอินสแตนซ์นิรนามของคลาสTimerหรือ - การประกาศฟังก์ชันสำหรับฟังก์ชัน
keeperที่ส่งคืนอ็อบเจ็กต์ประเภทหนึ่งTimeKeeperและมีพารามิเตอร์เดียว (ที่ไม่มีชื่อ) ซึ่งมีประเภทเป็นฟังก์ชัน (ตัวชี้ไปยัง) [หมายเหตุ 1 ]ที่ไม่รับอินพุตและส่งคืนTimerอ็อบเจ็กต์
มาตรฐานC++กำหนดให้ใช้การตีความแบบที่สอง ซึ่งไม่สอดคล้องกับบรรทัดที่ 13 ต่อไปนี้ ตัวอย่างเช่นClang++เตือนว่ามีการใช้การแยกวิเคราะห์ที่ยุ่งยากที่สุดในบรรทัดที่ 12 และเกิดข้อผิดพลาดในบรรทัดที่ 13 ต่อไปนี้: [ 4 ]
$ clang++timekeeper.cctimekeeper.cc:12:27: คำเตือน:วงเล็บถูกตีความว่าเป็นการประกาศฟังก์ชัน [-Wvexing-parse] ^~~~~~~~~ timekeeper.cc:12:28: หมายเหตุ:เพิ่มวงเล็บคู่หนึ่งเพื่อประกาศตัวแปร ^ ( ) timekeeper.cc:13:23: ข้อผิดพลาด:ประเภทฐานอ้างอิงสมาชิก ' ' ไม่ใช่โครงสร้างหรือยูเนียน~~~~~~~~~~~^~~~~~~~~ เกิดคำเตือน 1 รายการและข้อผิดพลาด 1 รายการTimeKeeperkeeper(Timer());TimeKeeperkeeper(Timer());TimeKeeper(Timer(*)())returnkeeper.getTime();
โซลูชัน
การตีความที่จำเป็นของการประกาศที่คลุมเครือเหล่านี้มักจะไม่ใช่การตีความที่ตั้งใจไว้[ 5 ] [ 6 ] ประเภทฟังก์ชันใน C++ มักจะซ่อนอยู่หลังtypedefsและโดยทั่วไปจะมีตัวระบุการอ้างอิงหรือ ตัว ชี้ที่ ชัดเจน เพื่อบังคับให้มีการตีความทางเลือก เทคนิคทั่วไปคือไวยากรณ์การสร้างหรือการแปลงวัตถุที่แตกต่างกัน
ในตัวอย่างการแปลงชนิดข้อมูล มีไวยากรณ์ทางเลือกสองแบบสำหรับการแปลงชนิดข้อมูล ได้แก่ "การแปลงชนิดข้อมูลแบบ C"
// มีการประกาศตัวแปรชนิด int int i (( int ) x );หรือนักแสดงที่มีชื่อเสียง:
int i ( static_cast < int > ( x ));ไวยากรณ์อีกแบบหนึ่ง ซึ่งใช้ได้ในภาษาซีเช่นกัน คือการใช้=เมื่อเริ่มต้นค่าตัวแปร:
int i = int ( x );ในตัวอย่างการประกาศตัวแปร วิธีการอื่น (ตั้งแต่ C++11) คือการเริ่มต้นแบบสม่ำเสมอ (วงเล็บปีกกา) [ 7 ] วิธีนี้ช่วยให้สามารถละเว้นชื่อประเภทได้ในระดับจำกัดเช่นกัน
// วิธีใดวิธีหนึ่งต่อไปนี้ใช้ได้: TimeKeeper keeper ( Timer {}); TimeKeeper keeper { Timer ()}; TimeKeeper keeper { Timer {}}; TimeKeeper keeper ({}); TimeKeeper keeper {{}};ก่อน C++11 เทคนิคทั่วไปในการบังคับการตีความที่ตั้งใจไว้คือการใช้วงเล็บพิเศษหรือการเริ่มต้นสำเนา: [ 6 ]
TimeKeeper time_keeper ( /*หลีกเลี่ยง MVP*/ ( Timer ()) ); TimeKeeper time_keeper = TimeKeeper ( Timer ());ในไวยากรณ์แบบหลัง การเริ่มต้นสำเนามีแนวโน้มที่จะถูกปรับให้เหมาะสมโดยคอมไพเลอร์[ 8 ] ตั้งแต่ C ++17 เป็นต้นมา การปรับให้เหมาะสมนี้ได้รับการรับประกัน[ 9 ]
หมายเหตุ
- ↑ตาม กฎ การลดทอนชนิด ของ C++ อ็อบเจ็กต์ฟังก์ชันที่ประกาศเป็นพารามิเตอร์จะเทียบเท่ากับพอยเตอร์ไปยังฟังก์ชันของชนิดนั้น ดูอ็อบเจ็กต์ฟังก์ชัน#ในภาษา C และ C ++
ลิงก์ภายนอก
- "ISO/IEC 14882:2003"องค์การมาตรฐานสากล จัดเก็บจากต้นฉบับเมื่อวันที่ 13 สิงหาคม 2021 เรียกดูเมื่อวันที่ 23 พฤศจิกายน 2018
- "8.2 การแก้ไขความกำกวม[dcl.ambig.res] ". อ้างอิงจากแหล่งเดียวกัน (PDF ): การอภิปรายในร่างมาตรฐาน C++03 ฉบับสุดท้าย
- "การเริ่มต้นโดยตรง" . CppReference . เก็บถาวรจากต้นฉบับเมื่อ 2021-12-13: ชนิดข้อมูลที่อ่อนไหวต่อการวิเคราะห์ที่ยุ่งยากที่สุด