กลับไปหน้าบทความ

อ่าน 4 นาที

การแยกวิเคราะห์ที่น่าหงุดหงิดที่สุด

ความคลุมเครือ/ซี++/CS1 maint: บริการเก็บถาวรที่เลิกใช้แล้ว

การแยกวิเคราะห์ที่ยุ่งยากที่สุดคือการแก้ไขความกำกวม ที่ไม่เป็นไปตามสัญชาตญาณ ในC++ในบางสถานการณ์ ไวยากรณ์ของ C++ ไม่สามารถแยกแยะระหว่างการเริ่มต้นพารามิเตอร์ของวัตถุการประกาศวัตถุ

การแยกวิเคราะห์ที่น่าหงุดหงิดที่สุด

การแยกวิเคราะห์ที่ยุ่งยากที่สุดคือการแก้ไขความกำกวม ที่ไม่เป็นไปตามสัญชาตญาณ ใน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());

  1. นิยามตัวแปรสำหรับตัวแปรkeeperของคลาสTimeKeeperซึ่งเริ่มต้นด้วยอินสแตนซ์นิรนามของคลาสTimerหรือ
  2. การประกาศฟังก์ชันสำหรับฟังก์ชัน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 ]

หมายเหตุ

  1. ตาม กฎ การลดทอนชนิด ของ C++ อ็อบเจ็กต์ฟังก์ชันที่ประกาศเป็นพารามิเตอร์จะเทียบเท่ากับพอยเตอร์ไปยังฟังก์ชันของชนิดนั้น ดูอ็อบเจ็กต์ฟังก์ชัน#ในภาษา C และ C ++
  • "ISO/IEC 14882:2003"องค์การมาตรฐานสากล จัดเก็บจากต้นฉบับเมื่อวันที่ 13 สิงหาคม 2021 เรียกดูเมื่อวันที่ 23 พฤศจิกายน 2018
    • "8.2 การแก้ไขความกำกวม[dcl.ambig.res] ". อ้างอิงจากแหล่งเดียวกัน (PDF ): การอภิปรายในร่างมาตรฐาน C++03 ฉบับสุดท้าย
  • "การเริ่มต้นโดยตรง" . CppReference . เก็บถาวรจากต้นฉบับเมื่อ 2021-12-13: ชนิดข้อมูลที่อ่อนไหวต่อการวิเคราะห์ที่ยุ่งยากที่สุด
ดึงข้อมูลมาจาก " https://en.wikipedia.org/w/index.php?title=Most_vexing_parse&oldid=1349779360 "

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ การแยกวิเคราะห์ที่น่าหงุดหงิดที่สุด

การแยกวิเคราะห์ที่ยุ่งยากที่สุดคือการแก้ไขความกำกวม ที่ไม่เป็นไปตามสัญชาตญาณ ในC++ในบางสถานการณ์ ไวยากรณ์ของ C++ ไม่สามารถแยกแยะระหว่างการเริ่มต้นพารามิเตอร์ของวัตถุการประกาศวัตถุ

หล่อแบบซี

ตัวอย่างง่ายๆ จะปรากฏขึ้นเมื่อต้องการแปลงประเภทข้อมูลโดยใช้ฟังก์ชันเพื่อแปลงนิพจน์สำหรับการกำหนดค่าเริ่มต้นให้กับตัวแปร:

โซลูชัน

การตีความที่จำเป็นของการประกาศที่คลุมเครือเหล่านี้มักจะไม่ใช่การตีความที่ตั้งใจไว้ [ 5 ] [ 6 ] ประเภทฟังก์ชันใน C++ มักจะซ่อนอยู่หลัง typedefs และโดยทั่วไปจะมี ตัวระบุการอ้างอิง หรือ ตัว ชี้ที่ ชัดเจน เพื่อบังคับให้มีการตีความทางเลือก...

หมายเหตุ

↑ ตาม กฎ การลดทอนชนิด ของ C++ อ็อบเจ็กต์ฟังก์ชันที่ประกาศเป็นพารามิเตอร์จะเทียบเท่ากับพอยเตอร์ไปยังฟังก์ชันของชนิดนั้น ดู อ็อบเจ็กต์ฟังก์ชัน#ในภาษา C และ C ++