เหตุใดข้อความข้อผิดพลาดเทมเพลต C ++ จึงน่ากลัวมาก


28

เทมเพลต C ++ มีชื่อเสียงในด้านการสร้างข้อความแสดงข้อผิดพลาดที่ยาวและไม่สามารถอ่านได้ ฉันมีความคิดทั่วไปว่าทำไมข้อความแสดงข้อผิดพลาดแม่แบบใน C ++ จึงแย่มาก โดยพื้นฐานแล้วปัญหาคือว่าข้อผิดพลาดจะไม่ถูกเรียกจนกระทั่งคอมไพเลอร์พบไวยากรณ์ที่ไม่ได้รับการสนับสนุนจากบางประเภทในแม่แบบ ตัวอย่างเช่น:

template <class T>
void dosomething(T& x) { x += 5; }

หากTไม่รองรับ+=ผู้ปฏิบัติงานคอมไพเลอร์จะสร้างข้อความแสดงข้อผิดพลาด และหากสิ่งนี้เกิดขึ้นลึกลงไปในห้องสมุดบางแห่งข้อความแสดงข้อผิดพลาดอาจยาวหลายพันบรรทัด

แต่แม่แบบ C ++ นั้นเป็นเพียงกลไกสำหรับการพิมพ์เป็ดเวลาคอมไพล์ ข้อผิดพลาดแม่แบบ C ++ เป็นแนวคิดคล้ายกับข้อผิดพลาดประเภทรันไทม์ที่อาจเกิดขึ้นในภาษาแบบไดนามิกเช่น Python ตัวอย่างเช่นพิจารณารหัสไพ ธ อนต่อไปนี้:

def dosomething(x):
   x.foo()

ที่นี่หากxไม่มีfoo()วิธีใดตัวแปลภาษา Python จะแสดงข้อยกเว้นและแสดงการติดตามสแต็กพร้อมกับข้อความแสดงข้อผิดพลาดที่ค่อนข้างชัดเจนซึ่งระบุถึงปัญหา แม้ว่าข้อผิดพลาดจะไม่ถูกเรียกใช้จนกว่าล่ามจะอยู่ลึกเข้าไปในฟังก์ชั่นของห้องสมุด แต่ข้อความข้อผิดพลาดเกี่ยวกับรันไทม์ยังไม่ใกล้เคียงเท่าที่อาเจียนไม่สามารถอ่านได้โดยคอมไพเลอร์ C ++ ทั่วไป แล้วทำไมคอมไพเลอร์ C ++ ไม่สามารถบอกได้ชัดเจนว่าเกิดอะไรขึ้น เหตุใดข้อความข้อผิดพลาดเทมเพลต C ++ บางรายการทำให้หน้าต่างคอนโซลของฉันเลื่อนขึ้นเป็นเวลา 5 วินาที


6
คอมไพเลอร์บางคนมีข้อความแสดงข้อผิดพลาดที่น่ากลัว แต่อื่น ๆ นั้นดีจริงๆ ( clang++ขยิบตากะพริบ)
Benjamin Bannier

2
ดังนั้นคุณต้องการให้โปรแกรมของคุณล้มเหลวขณะรันไทม์จัดส่งอยู่ในมือของลูกค้าแทนที่จะล้มเหลวในเวลารวบรวม?
P Shved

13
@Pavel ไม่ คำถามนี้ไม่เกี่ยวกับข้อดี / ข้อเสียของการตรวจสอบข้อผิดพลาดรันไทม์ vs เวลารวบรวม
Channel72

1
เป็นตัวอย่างของข้อผิดพลาดแม่แบบ C ++ ขนาดใหญ่ FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

คำตอบ:


28

ข้อความแสดงข้อผิดพลาดของแม่แบบอาจมีชื่อเสียง แต่ก็ไม่ได้ยาวและอ่านไม่ได้เสมอไป ในกรณีนี้ข้อความแสดงข้อผิดพลาดทั้งหมด (จาก gcc) คือ:

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

ในตัวอย่างของ Python คุณจะได้รับ "การติดตามสแต็ก" ของจุดอินสแตนซ์ของเท็มเพลตและข้อความแสดงข้อผิดพลาดที่ชัดเจนที่บ่งชี้ถึงปัญหา

บางครั้งข้อความแสดงข้อผิดพลาดที่เกี่ยวข้องกับแม่แบบอาจนานขึ้นด้วยเหตุผลหลายประการ:

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

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

นอกจากนี้การตรวจสอบที่รันไทม์หมายความว่าโปรแกรมจะหยุดข้อผิดพลาดแรกที่พบเท่านั้นที่แสดงข้อความเดียว คอมไพเลอร์อาจแสดงข้อผิดพลาดทั้งหมดที่พบจนกว่าจะหมด อย่างน้อยใน C ++ มันไม่ควรหยุดในข้อผิดพลาดแรกในไฟล์เนื่องจากอาจเป็นผลมาจากข้อผิดพลาดในภายหลัง


4
คุณสามารถยกตัวอย่างข้อผิดพลาดที่เป็นผลมาจากข้อผิดพลาดในภายหลังได้หรือไม่?
Ruslan

12

เหตุผลบางประการที่ชัดเจน ได้แก่ :

  1. ประวัติศาสตร์ เมื่อ gcc, MSVC ฯลฯ เป็นสิ่งใหม่พวกเขาไม่สามารถใช้พื้นที่เพิ่มเติมจำนวนมากในการจัดเก็บข้อมูลเพื่อสร้างข้อความแสดงข้อผิดพลาดที่ดีขึ้น ความทรงจำไม่เพียงพอที่พวกเขาทำไม่ได้
  2. เป็นเวลาหลายปีที่ผู้บริโภคไม่สนใจคุณภาพของข้อความแสดงข้อผิดพลาดดังนั้นผู้ขายส่วนใหญ่ก็ทำเช่นกัน
  3. ด้วยโค้ดบางตัวคอมไพเลอร์สามารถซิงโครไนซ์อีกครั้งและวินิจฉัยข้อผิดพลาดจริงในภายหลังในรหัส ข้อผิดพลาดในเทมเพลตเรียงซ้อนกันอย่างมากจนสิ่งใดก็ตามที่ผ่านมาเป็นสิ่งที่ไร้ประโยชน์เกือบตลอดเวลา
  4. ความยืดหยุ่นทั่วไปของแม่แบบทำให้ยากที่จะคาดเดาสิ่งที่คุณอาจหมายถึงเมื่อรหัสของคุณมีข้อผิดพลาด
  5. ภายในเทมเพลตความหมายของชื่อขึ้นอยู่กับบริบทของเทมเพลตและบริบทของการสร้างอินสแตนซ์และการค้นหาการอ้างถึงอาร์กิวเมนต์สามารถเพิ่มความเป็นไปได้มากขึ้น
  6. ฟังก์ชั่นการรับภาระมากเกินไปสามารถให้ผู้สมัครจำนวนมากสำหรับสิ่งที่เรียกฟังก์ชั่นโดยเฉพาะอาจหมายถึงและบางคอมไพเลอร์ (เช่น gcc) ตามรายการทุกหน้าที่เมื่อมีความกำกวม
  7. ผู้เขียนโค้ดจำนวนมากที่ไม่เคยพิจารณาการใช้พารามิเตอร์ปกติโดยไม่มั่นใจว่าค่าที่ส่งผ่านนั้นตรงตามข้อกำหนดอย่าพยายามตรวจสอบพารามิเตอร์เทมเพลตเลย (และฉันต้องยอมรับว่าฉันยอมรับฉันเอง)

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

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


9

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

นอกจากนี้เมื่อคุณชี้ให้เห็น Python interpreter สามารถทำให้คุณง่ายขึ้นมากโดยการแสดง stack trace เพราะมันเป็นการตีความรหัส Python หากคอมไพเลอร์ C ++ พบข้อผิดพลาดของเทมเพลตและให้การติดตามสแต็กกับคุณนั่นจะไม่เป็นประโยชน์เช่นเดียวกับ "เทมเพลตอาเจียน" ใช่ไหม

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