หนึ่งควรสืบทอด / สืบทอดมาจาก std :: exception?


15

ในขณะที่ออกแบบไลบรารี C ++ 'จริงจัง' ครั้งแรกของฉันฉันถามตัวเอง:

มันเป็นสไตล์ที่ดีหรือไม่ที่จะได้รับการยกเว้นจากสิ่งstd::exceptionนั้น

แม้หลังจากอ่านเสร็จ

ฉันยังไม่แน่ใจ เพราะนอกเหนือจากการปฏิบัติทั่วไป (แต่อาจจะไม่ดี) ฉันจะถือว่าในฐานะผู้ใช้ห้องสมุดว่าฟังก์ชั่นห้องสมุดจะstd::exceptionแสดงเฉพาะเมื่อฟังก์ชั่นไลบรารีมาตรฐานล้มเหลวในการใช้งานไลบรารีและมันไม่สามารถทำอะไรได้เลย แต่ยังคงเมื่อเขียนรหัสโปรแกรมสำหรับฉันมันสะดวกมากและยัง IMHO std::runtime_errorดูดีเพียงแค่โยน ผู้ใช้ของฉันยังสามารถพึ่งพาส่วนต่อประสานขั้นต่ำที่กำหนดเช่นwhat()รหัสหรือ

และตัวอย่างเช่นผู้ใช้ของฉันให้ข้อโต้แย้งที่ผิดพลาดสิ่งที่จะสะดวกกว่าการโยน a std::invalid_argumentใช่มั้ย ดังนั้นรวมกับการใช้งานยังร่วมกันของมาตรฐาน :: ข้อยกเว้นที่ฉันเห็นในรหัสผู้อื่น: ทำไมไม่ไปให้ดียิ่งขึ้นและได้รับมาจากชั้นยกเว้นของคุณเอง (เช่น lib_foo_exception) std::exceptionและจาก

คิด?


ฉันไม่แน่ใจว่าฉันทำตาม เพียงเพราะคุณได้รับมรดกจากstd::exceptionไม่ได้หมายความว่าคุณโยน std::exceptionนอกจากนี้std::runtime_errorจะสืบทอดมาจากstd::exceptionในสถานที่แรกและwhat()วิธีการมาจากการไม่ได้std::exception และแน่นอนคุณควรสร้างคลาสยกเว้นของคุณเองแทนการขว้างปาข้อยกเว้นทั่วไปเช่นstd::runtime_error std::runtime_error
Vincent Savard

3
ความแตกต่างคือเมื่อlib_foo_exceptionชั้นเรียนของฉันมาจากstd::exceptionผู้ใช้ห้องสมุดจะจับlib_foo_exceptionโดยเพียงแค่การจับstd::exceptionนอกเหนือไปจากเมื่อเขาจับเพียงหนึ่ง libary ดังนั้นผมจึงยังสามารถขอควรห้องสมุดยกเว้นระดับรากมรดกของฉันจากมาตรฐาน :: ข้อยกเว้น
Superlokkus

3
@LightnessRacesinOrbit ฉันหมายถึง "... นอกเหนือจาก" เช่น "จับได้กี่วิธีlib_foo_exception" ด้วยการสืบทอดจากstd::exceptionคุณสามารถทำมันด้วยหรือcatch(std::exception) catch(lib_foo_exception)โดยไม่ต้องเกิดจากการstd::exceptionที่คุณจะจับมันและถ้าหากcatch(lib_foo_exception)โดย
Superlokkus

2
@Superlokkus: catch(...)เราเรียงลำดับของการละเว้น มันเป็นเพราะภาษาอนุญาตให้สำหรับกรณีที่คุณกำลังพิจารณา (และสำหรับห้องสมุด "ทำงานผิดปกติ") แต่นั่นไม่ใช่วิธีปฏิบัติที่ดีที่สุดที่ทันสมัย
การแข่งขัน Lightness กับ Monica

1
การออกแบบการจัดการข้อยกเว้นจำนวนมากใน C ++ มีแนวโน้มที่จะสนับสนุน coarser, catchไซต์ทั่วไปมากขึ้นและการทำธุรกรรม coarser ที่เหมือนกันซึ่งเป็นตัวอย่างการดำเนินการของผู้ใช้ หากคุณเปรียบเทียบกับภาษาที่ไม่สนับสนุนความคิดในการจับแบบทั่วไปstd::exception&เช่นพวกเขามักจะมีรหัสมากขึ้นด้วยtry/catchบล็อกตัวกลางที่เกี่ยวข้องกับข้อผิดพลาดที่เฉพาะเจาะจงมากซึ่งจะลดการจัดการข้อยกเว้นโดยทั่วไปเมื่อเริ่มต้น การเน้นหนักมากขึ้นในการจัดการข้อผิดพลาดด้วยตนเองและในข้อผิดพลาดที่แตกต่างกันทั้งหมดที่อาจเกิดขึ้น

คำตอบ:


29

std::exceptionข้อยกเว้นทุกคนควรได้รับมรดกจาก

ตัวอย่างเช่นสมมติว่าฉันต้องโทรหาComplexOperationThatCouldFailABunchOfWays()และฉันต้องการจัดการกับข้อยกเว้นใด ๆ ที่อาจเกิดขึ้น หากทุกอย่างสืบทอดมาstd::exceptionนี่เป็นเรื่องง่าย ฉันต้องการเพียงcatchบล็อกเดียวและฉันมีอินเทอร์เฟซมาตรฐาน ( what()) สำหรับรับรายละเอียด

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

หากข้อยกเว้นไม่ได้รับมรดกstd::exceptionสิ่งนี้จะน่าเกลียดมาก:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

เกี่ยวกับว่าจะโยนruntime_errorหรือinvalid_argumentสร้างstd::exceptionคลาสย่อยของคุณเองเพื่อโยน: กฎทั่วไปของฉันคือการแนะนำคลาสย่อยใหม่เมื่อใดก็ตามที่ฉันต้องการจัดการข้อผิดพลาดบางประเภทแตกต่างจากข้อผิดพลาดอื่น ๆ (เช่นเมื่อใดก็ตามที่ฉันต้องการcatchบล็อกแยก)

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

หลักเกณฑ์C ++ Coreมีการอภิปรายและตัวอย่างเพิ่มเติม


เครื่องหมายทั้งหมดเหล่านั้น! C ++ แปลก
SuperJedi224

2
@ SuperJedi224 และตัวอักษรที่แตกต่างกันทั้งหมด! ภาษาอังกฤษแปลก
johannes

เหตุผลนี้ไม่สมเหตุสมผลสำหรับฉัน นี่ไม่ใช่สิ่งที่catch (...)(สำหรับจุดไข่ปลาที่แท้จริง) มีไว้เพื่ออะไร?
Maxpm

1
@ Maxpm catch (...)จะมีประโยชน์ก็ต่อเมื่อคุณไม่จำเป็นต้องทำอะไรกับสิ่งที่ขว้างไป หากคุณต้องการทำบางสิ่งเช่นแสดงหรือบันทึกข้อความแสดงข้อผิดพลาดตามตัวอย่างของฉันคุณต้องรู้ว่ามันคืออะไร
Josh Kelley

9

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

นั่นคือสมมติฐานที่ไม่ถูกต้อง

ประเภทข้อยกเว้นมาตรฐานมีไว้สำหรับการใช้ "สามัญ" พวกเขาจะไม่ได้ออกแบบให้เพียงนำมาใช้โดยห้องสมุดมาตรฐาน

std::exceptionใช่ทำให้ทุกอย่างได้รับมรดกจากท้าย บ่อยครั้งที่จะเกี่ยวข้องกับการสืบทอดจากหรือstd::runtime_error std::logic_errorสิ่งที่เหมาะสมสำหรับประเภทของข้อยกเว้นที่คุณใช้

นี่เป็นเรื่องของอัตนัย - ห้องสมุดยอดนิยมหลายแห่งไม่สนใจประเภทยกเว้นมาตรฐานทั้งหมดอย่างน่าจะเป็นการแยกไลบรารีออกจากไลบรารีมาตรฐาน โดยส่วนตัวแล้วฉันคิดว่านี่เป็นสิ่งที่เห็นแก่ตัวมาก! มันทำให้เกิดข้อยกเว้นที่ยากขึ้นกว่าเดิม

พูดโดยส่วนตัวฉันมักจะโยนstd::runtime_errorและจะทำมัน แต่นั่นคือการพูดคุยเกี่ยวกับวิธีที่ละเอียดในการสร้างคลาสการยกเว้นของคุณซึ่งไม่ใช่สิ่งที่คุณต้องการ

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