การใช้งานข้อยกเว้นในภาษา C ++


16

isocpp.org ข้อยกเว้น FAQระบุสถานะ

อย่าใช้ Throw เพื่อระบุข้อผิดพลาดในการใช้งานฟังก์ชั่น ใช้ assert หรือกลไกอื่น ๆ เพื่อส่งกระบวนการไปยังดีบักเกอร์หรือเพื่อทำให้กระบวนการทำงานผิดพลาดและเก็บรวบรวมดัมพ์ของข้อผิดพลาดเพื่อให้นักพัฒนาทำการดีบัก

ในทางกลับกันไลบรารีมาตรฐานจะกำหนด std :: logic_error และทั้งหมดเป็นอนุพันธ์ซึ่งดูเหมือนว่าฉันควรจะจัดการนอกเหนือจากสิ่งอื่น ๆ ข้อผิดพลาดในการเขียนโปรแกรม กำลังส่งสตริงว่างไปยัง std :: stof (จะโยน invalid_argument) ไม่ใช่ข้อผิดพลาดในการเขียนโปรแกรมหรือไม่? การส่งสตริงที่มีอักขระที่แตกต่างจาก '1' / '0' ถึง std :: bitset (จะส่ง invalid__chument) ไม่ใช่ข้อผิดพลาดในการเขียนโปรแกรมหรือไม่? กำลังเรียก std :: bitset :: set ด้วยดัชนีที่ไม่ถูกต้อง (จะโยน out_of_range) ไม่ใช่ข้อผิดพลาดในการเขียนโปรแกรมหรือไม่? หากสิ่งเหล่านี้ไม่ใช่ข้อผิดพลาดในการเขียนโปรแกรมที่จะทดสอบคืออะไร ตัวสร้างสตริงที่ใช้ std :: bitset มีอยู่ตั้งแต่ C ++ 11 เท่านั้นดังนั้นจึงควรได้รับการออกแบบโดยใช้ข้อยกเว้นในใจ ในทางตรงกันข้ามฉันมีคนบอกฉันว่า logic_error โดยทั่วไปไม่ควรใช้เลย

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

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


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

1
@ RobertHarvey คำจำกัดความนั้นยังคงมีปัญหาเดียวกัน - หากบางสิ่งสามารถแก้ไขได้โดยปราศจากการแทรกแซงของมนุษย์หรือไม่รู้จักเลเยอร์บนของโปรแกรม
cooky451

1
คุณกำลังถูกแขวนคอในด้านกฎหมาย ประเมินข้อดีข้อเสียและตัดสินใจด้วยตัวเอง นอกจากนี้ย่อหน้าสุดท้ายในคำถามของคุณ ... ฉันไม่คิดว่าตัวเองชัดเจนเลย ความคิดของคุณเป็นสีดำและขาวมากเมื่อความจริงน่าจะใกล้กับสีเทามาก
Robert Harvey

4
คุณพยายามทำวิจัยก่อนถามคำถามนี้หรือไม่? การจัดการข้อผิดพลาด C ++ สำนวนจะกล่าวถึงในรายละเอียดที่น่ารำคาญบนเว็บ การอ้างอิงถึงหนึ่งคำถามที่พบบ่อยหนึ่งรายการไม่ได้ทำการวิจัยที่ดี หลังจากทำวิจัยแล้วคุณยังต้องคิดเอง อย่าให้ฉันเริ่มต้นด้วยวิธีที่โรงเรียนสอนการเขียนโปรแกรมของเราสร้างหุ่นยนต์เข้ารหัสรูปแบบซอฟต์แวร์ที่ไม่สนใจซึ่งไม่รู้วิธีคิดด้วยตนเอง
Robert Harvey

2
ซึ่งให้ความเชื่อถือกับทฤษฎีของฉันว่ากฎดังกล่าวอาจไม่มีอยู่จริง ฉันได้เชิญผู้คนจากThe C ++ Loungeเพื่อดูว่าพวกเขาสามารถตอบคำถามของคุณได้หรือไม่แม้ว่าทุกครั้งที่ฉันไปที่นั่นคำแนะนำของพวกเขาคือ "หยุดใช้ C ++ มันจะทำให้สมองของคุณว่างเปล่า" ดังนั้นรับความเสี่ยงของคุณเอง
Robert Harvey

คำตอบ:


15

ครั้งแรกฉันรู้สึกว่าจำเป็นต้องชี้ให้เห็นว่าstd::exceptionและลูก ๆ ของมันถูกออกแบบมานานแล้ว มีหลายส่วนที่อาจจะแตกต่างกัน (ถ้ามี) ในปัจจุบัน

อย่าเข้าใจฉันผิด: มีบางส่วนของการออกแบบที่ทำงานออกมาได้ดีและเป็นตัวอย่างที่ดีของวิธีการออกแบบลำดับชั้นข้อยกเว้นสำหรับ C ++ (เช่นความจริงที่ว่าแตกต่างจากคลาสอื่น ๆ ทั้งหมด รูททั่วไป)

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

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

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

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

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

สำหรับวิธีที่คุณตัดสินใจ: ฉันไม่คิดว่าจะมีคำตอบง่าย ๆ สำหรับสถานการณ์ที่ดีขึ้นหรือแย่ลงไม่ใช่เรื่องง่ายที่จะวัด ในขณะที่มีบางกรณีที่ตัดสินใจได้ง่ายจะต้องเป็นพิเศษ [un] มี (และอาจจะเป็น) กรณีที่เปิดคำถามหรือต้องการความรู้เกี่ยวกับบริบทที่อยู่นอกขอบเขตของฟังก์ชันในมือ สำหรับกรณีเช่นนี้อย่างน้อยมันอาจคุ้มค่าที่จะพิจารณาการออกแบบที่คล้ายกับส่วนหนึ่งของ iostreams ซึ่งผู้ใช้สามารถตัดสินใจได้ว่าความล้มเหลวส่งผลให้เกิดข้อยกเว้นที่เกิดขึ้นหรือไม่ หรืออาจเป็นไปได้ที่จะมีฟังก์ชั่นสองชุดแยกกัน (หรือคลาส ฯลฯ ) โดยหนึ่งในนั้นจะมีข้อยกเว้นเพื่อระบุความล้มเหลวอีกชุดหนึ่งใช้วิธีการอื่น ถ้าคุณไปเส้นทางนั้น


9

ตัวสร้างสตริงที่ใช้ std :: bitset มีอยู่ตั้งแต่ C ++ 11 เท่านั้นดังนั้นจึงควรได้รับการออกแบบด้วยการใช้ข้อยกเว้นในใจ ในทางกลับกันฉันมีคนบอกฉันว่า logic_error ไม่ควรใช้เลย

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

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

ไลบรารีมาตรฐานใช้ทฤษฎีที่ให้ความสามารถในการจับและการจัดการที่เป็นไปได้ข้อผิดพลาดมีความสำคัญมากกว่า debuggability

อาจจะยอดเยี่ยมอาจจะไม่ ฟังก์ชั่นของตัวเองโดยทั่วไปไม่สามารถรู้ได้แน่นอนว่ามันไม่มีความคิดในสิ่งที่มันถูกเรียก

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

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

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

คุณสามารถโยนข้อยกเว้น:

  1. ไม่เคย
  2. ทุกที่
  3. เฉพาะกับข้อผิดพลาดของโปรแกรมเมอร์
  4. ไม่เคยมีข้อผิดพลาดโปรแกรมเมอร์
  5. เฉพาะในช่วงความล้มเหลวที่ไม่ธรรมดา (พิเศษ)

และหาคนบนอินเทอร์เน็ตที่เห็นด้วยกับคุณ คุณจะต้องยอมรับสไตล์ที่เหมาะกับคุณ


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

1
@Jules - ตอนนี้ (การแสดง) สมควรได้รับคำตอบแน่นอนเมื่อคุณสำรองข้อมูลการเรียกร้องของคุณ ประสิทธิภาพการยกเว้น C ++ เป็นปัญหาอย่างแน่นอนอาจมากกว่าหรือน้อยกว่าที่อื่น แต่การระบุ "C ++ ไม่ใช่หนึ่งในภาษาเหล่านั้น (ที่ข้อยกเว้นมีประสิทธิภาพต่ำ]" เป็นที่ถกเถียงกันอย่างแน่นอน
Martin Ba

1
@MartinBa - เปรียบเทียบกับพูดว่าประสิทธิภาพการยกเว้น Java, C ++ เป็นลำดับความสำคัญได้เร็วขึ้น เกณฑ์มาตรฐานแนะนำประสิทธิภาพการขว้างข้อยกเว้นขึ้น 1 ระดับจะช้ากว่าการจัดการค่าตอบแทนใน C ++ ประมาณ 50x เทียบกับช้ากว่า 1000 เท่าใน Java คำแนะนำที่เขียนขึ้นสำหรับ Java ในกรณีนี้ไม่ควรนำไปใช้กับ C ++ โดยไม่ต้องคิดเพิ่มเพราะมีมากกว่าความแตกต่างของขนาดระหว่างประสิทธิภาพทั้งสอง บางทีฉันควรจะเขียน "ประสิทธิภาพต่ำมาก" มากกว่า "ประสิทธิภาพต่ำ"
จูลส์

1
@Jules - ขอบคุณสำหรับตัวเลขเหล่านี้ (แหล่งข้อมูลใด ๆ ) ฉันสามารถเชื่อว่าพวกเขาเพราะ Java (และ C #) จำเป็นที่จะต้องจับกองติดตามซึ่งแน่นอนดูเหมือนว่ามันจะมีราคาแพงจริงๆ ฉันยังคงคิดว่าการตอบสนองครั้งแรกของคุณเป็นเรื่องที่เข้าใจผิดเพราะแม้แต่การชะลอตัว 50x ก็ค่อนข้างหนักฉันคิดว่า esp ในภาษาเชิงประสิทธิภาพเช่น C ++
Martin Ba

2

มีการเขียนคำตอบที่ดีอื่น ๆ อีกมากมายฉันต้องการเพิ่มจุดสั้น ๆ

คำตอบดั้งเดิมโดยเฉพาะอย่างยิ่งเมื่อเขียนคำถามที่พบบ่อยเกี่ยวกับ ISO C ++ ส่วนใหญ่จะเปรียบเทียบ "ข้อยกเว้น C ++" กับ "รหัสส่งคืนแบบ C" ตัวเลือกที่สาม "คืนค่าคอมโพสิตบางประเภทเช่น a structหรือunionหรือทุกวันนี้boost::variantหรือ (เสนอ)std::expectedไม่ได้รับการพิจารณา

ก่อนหน้า C ++ 11 ตัวเลือก "return a composite type" นั้นมักจะอ่อนแอมาก เพราะไม่มีความหมายการย้ายดังนั้นการคัดลอกสิ่งต่าง ๆ เข้าและออกจากโครงสร้างอาจมีราคาแพงมาก มันเป็นสิ่งสำคัญอย่างยิ่งที่จุดนั้นในภาษาเพื่อจัดรูปแบบโค้ดของคุณไปยังRVOเพื่อให้ได้ประสิทธิภาพที่ดีที่สุด การยกเว้นเป็นเหมือนวิธีง่าย ๆ ในการคืนประเภทคอมโพสิตอย่างมีประสิทธิภาพเมื่อไม่เช่นนั้นจะค่อนข้างยาก

IMO หลัง C ++ 11 ตัวเลือกนี้ "คืนค่าการแยกสหภาพ" ซึ่งคล้ายกับสำนวน Result<T, E>ใช้ในสนิมทุกวันนี้ควรได้รับการสนับสนุนบ่อยครั้งในรหัส C ++ บางครั้งมันเป็นสไตล์ที่ง่ายและสะดวกกว่าในการระบุข้อผิดพลาด ด้วยข้อยกเว้นมีความเป็นไปได้เสมอที่ฟังก์ชันที่ไม่ได้ทำมาก่อนอาจเริ่มการขว้างปาหลังจากผู้สร้างซ้ำได้ทันทีและโปรแกรมเมอร์ไม่ได้บันทึกสิ่งต่าง ๆ ที่ดีเสมอไป เมื่อข้อผิดพลาดถูกระบุว่าเป็นส่วนหนึ่งของค่าตอบแทนในสหภาพที่แยกออกจากกันมันจะช่วยลดโอกาสที่โปรแกรมเมอร์จะไม่สนใจรหัสข้อผิดพลาดซึ่งเป็นคำวิจารณ์ปกติของการจัดการข้อผิดพลาดแบบ C

มักจะใช้Result<T, E>งานได้เช่นการเพิ่มตัวเลือก คุณสามารถทดสอบใช้operator boolหากเป็นค่าหรือข้อผิดพลาด จากนั้นใช้การพูดoperator *เพื่อเข้าถึงค่าหรือฟังก์ชั่น "รับ" อื่น ๆ โดยทั่วไปการเข้าถึงนั้นจะไม่ถูกตรวจสอบเพื่อความเร็ว แต่คุณสามารถทำได้ในการสร้างการตรวจแก้จุดบกพร่องการเข้าถึงจะถูกตรวจสอบและการยืนยันทำให้แน่ใจว่าจริง ๆ แล้วมีค่าไม่ใช่ข้อผิดพลาด วิธีนี้ทุกคนที่ไม่ได้ตรวจสอบข้อผิดพลาดอย่างถูกต้องจะได้รับการยืนยันอย่างหนักมากกว่าปัญหาที่ร้ายกาจมากกว่า

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

ฉันกลายเป็นแฟนตัวยงของสไตล์นี้ โดยปกติฉันมักจะใช้สิ่งนี้หรือข้อยกเว้น แต่ฉันพยายาม จำกัด ข้อยกเว้นให้กับปัญหาที่สำคัญ สำหรับบางอย่างเช่นข้อผิดพลาดในการแยกฉันพยายามที่จะกลับexpected<T>เช่น สิ่งที่ชอบstd::stoiและสิ่งที่boost::lexical_castทำให้เกิดข้อยกเว้น C ++ ในกรณีที่มีปัญหาเล็กน้อย "สตริงไม่สามารถแปลงเป็นตัวเลข" ดูเหมือนจะมีรสชาติแย่มากสำหรับฉันในปัจจุบัน


1
std::expectedยังเป็นข้อเสนอที่ไม่ยอมรับใช่ไหม
Martin Ba

คุณพูดถูกฉันคิดว่ามันยังไม่ได้รับการยอมรับ แต่มีการใช้งานโอเพนซอร์ซหลายตัวที่ลอยอยู่รอบ ๆ และฉันก็กลิ้งของฉันเองสองสามครั้งที่ฉันเดา มันซับซ้อนน้อยกว่าการทำประเภทตัวแปรเนื่องจากมีเพียงสองสถานะที่เป็นไปได้ ข้อควรพิจารณาในการออกแบบหลักคือต้องการอินเทอร์เฟซแบบไหนที่คุณต้องการและคุณต้องการให้มันเป็นเหมือนที่คาดไว้ <T> ของ Andrescu โดยที่วัตถุข้อผิดพลาดควรจะเป็นจริงexception_ptrหรือคุณต้องการใช้โครงสร้างบางประเภทหรือบางอย่าง เช่นนั้น.
Chris Beck

คำพูดของ Andrei Alexandrescu อยู่ที่นี่: channel9.msdn.com/Shows/Going+Deep/ ......เขาแสดงรายละเอียดวิธีสร้างคลาสเช่นนี้และสิ่งที่คุณควรพิจารณา
Chris Beck

ข้อเสนอที่เสนอ[[nodiscard]] attributeจะมีประโยชน์สำหรับวิธีการจัดการข้อผิดพลาดเนื่องจากจะทำให้แน่ใจได้ว่าคุณจะไม่เพิกเฉยต่อข้อผิดพลาดจากอุบัติเหตุ
CodesInChaos

- ใช่ฉันรู้คำพูดของเอเอ ฉันพบการออกแบบที่ค่อนข้างแปลกตั้งแต่แกะกล่อง ( except_ptr) คุณต้องโยนข้อยกเว้นภายใน โดยส่วนตัวแล้วฉันคิดว่าเครื่องมือดังกล่าวควรทำงานอย่างสมบูรณ์โดยไม่ต้องมีการประหารชีวิต เพียงคำพูด
Martin Ba

1

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

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

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

จากนั้นมาอัพเดททรัพยากร อย่างน้อยประเด็นนี้ก็คือความสัมพันธ์ที่ใกล้ชิดกับแง่มุมการดำเนินงานที่สำคัญของแอปพลิเคชัน ลองนึกภาพEmployeeคลาสที่มีฟังก์ชั่นUpdateDetails(std::string&)ซึ่งแก้ไขรายละเอียดตามสตริงที่คั่นด้วยเครื่องหมายจุลภาคที่กำหนด คล้ายกับการเปิดตัวหน่วยความจำที่ล้มเหลวฉันคิดว่ามันยากที่จะจินตนาการถึงการกำหนดค่าตัวแปรสมาชิกที่ล้มเหลวเนื่องจากขาดประสบการณ์ในโดเมนดังกล่าวซึ่งสิ่งเหล่านี้อาจเกิดขึ้นได้ อย่างไรก็ตามฟังก์ชั่นแบบUpdateDetailsAndUpdateFile(std::string&)ที่ทำตามชื่อบ่งบอกว่าคาดว่าจะล้มเหลว นี่คือสิ่งที่ฉันเรียกว่าการดำเนินการที่สำคัญ

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

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

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

ฉันเห็นคนจำนวนมากที่โดยทั่วไปแทนที่ค่าตอบแทนด้วยข้อยกเว้นเพราะพวกเขาใช้ C ++ และไม่ใช่ C และมันให้ 'การแยกข้อผิดพลาดที่ดีในการจัดการ' ฯลฯ และกระตุ้นให้ฉันหยุดภาษา 'ผสม' ฯลฯ ฉันมักจะอยู่ห่างจาก คนเช่นนี้


1

ครั้งแรกกับคนอื่น ๆ ได้กล่าวสิ่งที่ไม่ว่าชัดเจนใน C ++ IMHO ส่วนใหญ่เป็นเพราะความต้องการและหมอนรองจะค่อนข้างแตกต่างกันมากขึ้นใน C ++ กว่าภาษาอื่น ๆ ทาย C # และ Java ที่มีปัญหาข้อยกเว้น "ที่คล้ายกัน"

ฉันจะแสดงตัวอย่าง std :: stof:

ผ่านสตริงที่ว่างเปล่าไปยัง std :: stof (จะโยน invalid_argument) ไม่ใช่ข้อผิดพลาดในการเขียนโปรแกรม

สัญญาพื้นฐานที่ฉันเห็นมันของฟังก์ชั่นนี้คือมันพยายามที่จะแปลงมันเป็นข้อโต้แย้งในการลอยและความล้มเหลวในการทำเช่นนั้นจะถูกรายงานโดยข้อยกเว้น ข้อยกเว้นที่เป็นไปได้ทั้งสองอย่างนั้นมาจากlogic_errorแต่ไม่ใช่ในแง่ของความผิดพลาดของโปรแกรมเมอร์ แต่ในแง่ของ "ข้อมูลที่ไม่สามารถถูกแปลงเป็นแบบลอยได้"

ในที่นี้หนึ่งอาจบอกว่าlogic_errorใช้เพื่อระบุว่าเนื่องจากอินพุต (runtime) นั้นเป็นข้อผิดพลาดเสมอที่พยายามแปลงมัน - แต่มันเป็นหน้าที่ของฟังก์ชั่นในการพิจารณาและบอกคุณ (ผ่านข้อยกเว้น)

หมายเหตุด้านข้าง: ในมุมมองนั้น a runtime_error สามารถถูกมองว่าเป็นสิ่งที่ให้อินพุตเดียวกันกับฟังก์ชันสามารถประสบความสำเร็จในทางทฤษฎีสำหรับการทำงานที่แตกต่างกัน (เช่นการทำงานของไฟล์การเข้าถึงฐานข้อมูล ฯลฯ )

อีกด้านหมายเหตุ: ห้องสมุด C + + regex เลือกที่จะได้รับมันเป็นข้อผิดพลาดจากruntime_errorแม้ว่าจะมีกรณีที่สามารถจำแนกเช่นเดียวกับที่นี่ (รูปแบบ regex ไม่ถูกต้อง)

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

(*): นั่นไม่ได้หมายความว่ารหัสชิ้นเดียวไม่ควรสอดคล้องกัน แต่ไม่ว่าคุณจะโยนruntime_หรือlogic_หรือcustom_อะไรก็ตามมันไม่สำคัญเลย


หากต้องการแสดงความคิดเห็นทั้งstofและbitset:

ฟังก์ชั่นทั้งสองใช้สตริงเป็นอาร์กิวเมนต์และในทั้งสองกรณีมันคือ:

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

กฎที่เกิดขึ้นบ่อยครั้งพร้อมกับข้อยกเว้นคือ "ใช้ข้อยกเว้นเฉพาะในสถานการณ์พิเศษ" เท่านั้น แต่ฟังก์ชั่นห้องสมุดควรจะรู้ได้อย่างไรว่าสถานการณ์ใดเป็นพิเศษ?

คำสั่งนี้มี IMHO รากที่สอง:

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

ถิ่นที่อยู่ของการจัดการข้อผิดพลาด : ถ้าฟังก์ชั่นถูกเรียกและข้อยกเว้นที่ถูกจับได้ทันทีและประมวลผลแล้วมีจุดเล็ก ๆ ในการขว้างปายกเว้นเช่นจัดการข้อผิดพลาดจะมีมากขึ้นอย่างละเอียดกับกว่ากับcatchif

ตัวอย่าง:

float readOrDefault;
try {
  readOrDefault = stof(...);
} catch(std::exception&) {
  // discard execption, just use default value
  readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}

นี่คือที่ที่ฟังก์ชั่นเช่นTryParsevs. Parseเข้ามาเล่น: หนึ่งรุ่นสำหรับเมื่อรหัสท้องถิ่นคาดว่าสตริงการแจงจะถูกต้องหนึ่งรุ่นเมื่อรหัสท้องถิ่นสมมติว่ามันเป็นที่คาดหวังจริง(เช่นไม่พิเศษ) ที่การแยกวิเคราะห์จะล้มเหลว

อันที่จริงstofเป็นเพียงเสื้อคลุม (หมายถึง) strtofดังนั้นถ้าคุณไม่ต้องการข้อยกเว้นใช้อันนั้น


ดังนั้นฉันควรตัดสินใจอย่างไรว่าควรใช้ข้อยกเว้นหรือไม่สำหรับฟังก์ชั่นเฉพาะ

IMHO คุณมีสองกรณี:

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

  • ฟังก์ชั่น "แอปพลิเคชัน" (เฉพาะสำหรับหยดของรหัสแอปพลิเคชันอาจถูกนำกลับมาใช้ใหม่บางส่วน แต่ถูก จำกัด ด้วยรูปแบบการจัดการข้อผิดพลาดของแอพ ฯลฯ ): ที่นี่มันมักจะชัดเจน หากการเรียกใช้โค้ดโค๊ดฟังก์ชั่นจัดการกับข้อยกเว้นด้วยวิธีที่มีประโยชน์และมีประโยชน์ให้ใช้ข้อยกเว้นเพื่อรายงานข้อผิดพลาดใด ๆ(แต่ดูด้านล่าง) หากรหัสแอปพลิเคชันอ่านและเขียนได้ง่ายขึ้นสำหรับสไตล์การส่งคืนข้อผิดพลาดโดยทั้งหมดใช้วิธีนั้น

แน่นอนว่าจะมีสถานที่ระหว่าง - เพียงแค่ใช้สิ่งที่ต้องการและจำ YAGNI


สุดท้ายฉันคิดว่าฉันควรกลับไปที่คำสั่ง FAQ

อย่าใช้ Throw เพื่อระบุข้อผิดพลาดในการใช้งานฟังก์ชั่น ใช้ assert หรือกลไกอื่น ๆ เพื่อส่งกระบวนการไปยังดีบักเกอร์หรือเพื่อทำให้กระบวนการขัดข้อง ...

ฉันสมัครรับข้อมูลนี้สำหรับข้อผิดพลาดทั้งหมดที่บ่งชี้อย่างชัดเจนว่ามีบางอย่างเกิดความผิดพลาดอย่างรุนแรงหรือว่ารหัสการโทรอย่างชัดเจนไม่รู้ว่ากำลังทำอะไรอยู่

แต่เมื่อสิ่งนี้เหมาะสมมักเป็นแอพพลิเคชั่นที่มีความเฉพาะเจาะจงสูงดังนั้นโปรดดูเหนือไลบรารีโดเมนกับโดเมนแอปพลิเคชัน

สิ่งนี้ย้อนกลับไปที่คำถามว่าจะตรวจสอบเงื่อนไขการโทรได้หรือไม่ แต่ฉันจะไม่ตอบคำถามนั้นยาวเกินไป :-)

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