ฉันควรสืบทอดจาก std :: ข้อยกเว้นหรือไม่


99

ผมเคยเห็นแหล่งที่เชื่อถือได้อย่างน้อยหนึ่ง (คลาส c ++ ผมเอา) ขอแนะนำให้ใช้เฉพาะการเรียนการยกเว้นใน C ++ std::exceptionควรสืบทอดจาก ฉันไม่ชัดเจนเกี่ยวกับประโยชน์ของแนวทางนี้

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

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


คุณอาจต้องการดูสิ่งนี้: stackoverflow.com/questions/1605778/1605852#1605852
sbi

2
แม้ว่าจะเป็นบันทึกด้านข้างที่ไม่เกี่ยวข้องกับคำถามนั้น ๆ แต่คลาสC ++ ที่คุณใช้ไม่จำเป็นต้องเป็นแหล่งข้อมูลที่น่าเชื่อถือเกี่ยวกับแนวทางปฏิบัติที่ดีเพียงอย่างเดียวจากสิทธิของตนเอง
Christian Rau

คำตอบ:


69

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

std::exception<stdexcept>


21
ไม่มีวิธีส่งข้อความไปยัง std :: exception std :: runtime_error ยอมรับสตริงและได้มาจาก std :: exception
Martin York

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

std::exceptionถูกกำหนดไว้ใน<exception>ส่วนหัว ( พิสูจน์ ) -1
Alexander Shukaev

5
แล้วประเด็นของคุณคืออะไร? คำจำกัดความหมายถึงการประกาศคุณเห็นอะไรผิดที่นี่?
Nikolai Fetissov

40

ปัญหาstd::exceptionคือไม่มีตัวสร้าง (ในเวอร์ชันที่เป็นไปตามมาตรฐาน) ที่ยอมรับข้อความ

std::runtime_errorเป็นผลให้ฉันชอบที่จะเป็นผลมาจาก สิ่งนี้ได้มาจากstd::exceptionแต่ตัวสร้างของมันอนุญาตให้คุณส่งผ่าน C-String หรือ a std::stringไปยังตัวสร้างที่จะส่งคืน (เป็น a char const*) เมื่อwhat()ถูกเรียก


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

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

3
ฉันไม่รู้ว่าคุณคิดว่าข้อยกเว้นมีไว้เพื่อการบันทึกข้อมูลเท่านั้น :)
Emil

1
@ เอมิล: เราได้ผ่านข้อโต้แย้งนี้ไปแล้ว ที่ฉันชี้ให้เห็นว่านั่นไม่ใช่สิ่งที่คุณใส่ไว้ในข้อยกเว้น คุณเข้าใจเรื่องนี้ดี แต่ข้อโต้แย้งของคุณมีข้อบกพร่อง ข้อความแสดงข้อผิดพลาดที่สร้างขึ้นสำหรับผู้ใช้นั้นแตกต่างอย่างสิ้นเชิงกับข้อความบันทึกสำหรับนักพัฒนา
Martin York

1
@ เอมิล. การสนทนานี้มีอายุหนึ่งปี สร้างคำถามของคุณเอง ณ จุดนี้ ข้อโต้แย้งของคุณเท่าที่ฉันกังวลนั้นไม่ถูกต้อง (และขึ้นอยู่กับการโหวตที่ผู้คนมักจะเห็นด้วยกับฉัน) ดูข้อยกเว้น 'จำนวนดี' ที่จะนำไปใช้กับห้องสมุดของฉันคืออะไร และการจับข้อยกเว้นทั่วไปเป็นสิ่งที่ไม่ดีจริงหรือ? คุณไม่ได้ให้ข้อมูล nw ใด ๆ ตั้งแต่การเป็นสมาชิกคนก่อนของคุณ
Martin York

17

เหตุผลในการสืบทอดจากstd::exceptionมันคือ "มาตรฐาน" ชั้นฐานสำหรับข้อยกเว้นเพื่อให้มันเป็นธรรมชาติสำหรับคนอื่น ๆ std::exceptionในทีมสำหรับตัวอย่างเช่นการคาดหวังว่าและฐานจับ

หากคุณกำลังมองหาความสะดวกคุณสามารถสืบทอดจากstd::runtime_errorที่ให้คอนstd::stringสตรัคเตอร์


2
การได้มาจากประเภทข้อยกเว้นมาตรฐานอื่น ๆ ยกเว้น std :: ข้อยกเว้นอาจไม่ใช่ความคิดที่ดี ปัญหาคือพวกมันมาจาก std :: ข้อยกเว้นที่ไม่ใช่แทบซึ่งอาจนำไปสู่จุดบกพร่องที่ละเอียดอ่อนที่เกี่ยวข้องกับการสืบทอดหลายรายการโดยที่ catch (std :: exception &) อาจไม่สามารถตรวจจับข้อยกเว้นได้โดยไม่ต้องแจ้งให้ทราบเนื่องจากการแปลงเป็น std :: ข้อยกเว้น คลุมเครือ
Emil

2
ฉันอ่านบทความเกี่ยวกับการเพิ่มประสิทธิภาพในเรื่องนี้ มันดูสมเหตุสมผลในแง่ตรรกะล้วนๆ แต่ฉันไม่เคยเห็น MH ในข้อยกเว้นในป่า ฉันจะไม่พิจารณาใช้ MH ในข้อยกเว้นดังนั้นดูเหมือนว่าค้อนขนาดใหญ่จะแก้ไขปัญหาที่ไม่มีอยู่จริง หากนี่เป็นปัญหาจริงฉันมั่นใจว่าเราจะได้เห็นการดำเนินการจากคณะกรรมการมาตรฐานเพื่อแก้ไขข้อบกพร่องที่ชัดเจนดังกล่าว ดังนั้นความคิดเห็นของฉันก็คือ std :: runtime_error (และครอบครัว) ยังคงเป็นข้อยกเว้นที่ยอมรับได้อย่างสมบูรณ์ในการโยนหรือได้มาจาก
Martin York

5
@Emil: การได้มาจากข้อยกเว้นมาตรฐานอื่น ๆstd::exceptionเป็นความคิดที่ยอดเยี่ยม สิ่งที่คุณไม่ควรทำคือการสืบทอดจากมากขึ้นมากกว่าหนึ่ง
Mooing Duck

@MooingDuck: หากคุณได้มาจากประเภทข้อยกเว้นมาตรฐานมากกว่าหนึ่งประเภทคุณจะได้ std :: ข้อยกเว้นที่ได้รับ (ไม่ใช่แทบ) หลายครั้ง ดูboost.org/doc/libs/release/libs/exception/doc/...
Emil

1
@ เอมิล: ที่ตามมาจากที่ฉันพูด
หมูปิ้ง

13

ครั้งหนึ่งฉันเคยเข้าร่วมในการล้าง codebase ขนาดใหญ่ที่ผู้เขียนคนก่อนเคยโยน ints, HRESULTS, std :: string, char *, random class ... แค่ตั้งชื่อประเภทและอาจถูกโยนทิ้งที่ไหนสักแห่ง และไม่มีคลาสพื้นฐานทั่วไปเลย เชื่อฉันเถอะว่าสิ่งต่าง ๆ เป็นระเบียบมากขึ้นเมื่อเรามาถึงจุดที่ประเภทการโยนทั้งหมดมีฐานร่วมกันที่เราสามารถจับได้และไม่รู้ว่าจะไม่มีอะไรผ่านพ้นไปได้ ดังนั้นโปรดทำตัวเอง (และผู้ที่จะต้องรักษารหัสของคุณในอนาคต) ด้วยความโปรดปรานและทำแบบนั้นตั้งแต่เริ่มต้น


12

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


5
อย่างไรก็ตามโปรดทราบว่าการเพิ่ม :: ข้อยกเว้นนั้นไม่ได้มาจาก std :: ข้อยกเว้น แม้ว่าจะได้มาจากการเพิ่ม :: ข้อยกเว้นคุณควรได้รับจาก std :: ข้อยกเว้น (เป็นบันทึกแยกต่างหากเมื่อใดก็ตามที่ได้มาจากประเภทข้อยกเว้นคุณควรใช้การสืบทอดเสมือน)
Emil

10

std::exceptionใช่คุณควรจะได้รับจาก

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


6
+1 จุดดี ทั้งจากการแยกความกังวลและมุมมองของ i18n การปล่อยให้เลเยอร์การนำเสนอสร้างข้อความของผู้ใช้นั้นดีกว่าอย่างแน่นอน
John M Gant

@JohnMGant และ Emil: น่าสนใจคุณช่วยชี้ให้เห็นตัวอย่างที่เป็นรูปธรรมว่าสามารถทำได้อย่างไร ฉันเข้าใจว่าเราสามารถได้มาจากstd::exceptionและนำข้อมูลของข้อยกเว้น แต่ใครจะเป็นผู้รับผิดชอบในการสร้าง / จัดรูปแบบข้อความแสดงข้อผิดพลาด? ::what()ฟังก์ชั่นหรือสิ่งอื่นใด
alfC

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

9

เหตุผลที่คุณอาจต้องการสืบทอดจากstd::exceptionนั้นเป็นเพราะมันช่วยให้คุณสามารถโยนข้อยกเว้นที่ถูกจับได้ตามคลาสนั้น ๆ นั่นคือ:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

มีปัญหาหนึ่งเกี่ยวกับการสืบทอดที่คุณควรทราบคือการแบ่งส่วนวัตถุ เมื่อคุณเขียนthrow e;ที่มีการแสดงออกจากเส้นข้างต้นวัตถุชั่วคราวที่เรียกว่าข้อยกเว้นวัตถุชนิดของการที่จะถูกกำหนดโดยการลบใด ๆ ระดับบนสุด throwCV-บ่นจากประเภทคงที่ของตัวถูกดำเนินการของ นั่นอาจไม่ใช่สิ่งที่คุณคาดหวัง ตัวอย่างของปัญหาที่คุณสามารถหาที่นี่

ไม่ใช่การโต้แย้งเรื่องมรดก แต่เป็นเพียงข้อมูลที่ 'ต้องรู้'


3
ฉันคิดว่าสิ่งที่นำออกไปคือ "โยน e; เป็นสิ่งชั่วร้ายและ "โยน" ก็โอเค.
James Schek

2
ใช่ใช้ได้throw;แต่ไม่ชัดเจนว่าคุณควรเขียนอะไรเช่นนี้
Kirill V. Lyadvinsky

1
เป็นเรื่องที่น่าเจ็บปวดอย่างยิ่งสำหรับนักพัฒนา Java ที่ทำการ rethrow โดยใช้ "throw e;"
James Schek

พิจารณาเฉพาะที่มาจากประเภทฐานนามธรรม การจับประเภทฐานนามธรรมตามค่าจะทำให้คุณมีข้อผิดพลาด (หลีกเลี่ยงการแบ่งส่วน) การจับประเภทฐานนามธรรมโดยการอ้างอิงจากนั้นพยายามโยนสำเนาจะทำให้คุณมีข้อผิดพลาด (หลีกเลี่ยงการแบ่งส่วนอีกครั้ง)
เอมิล

นอกจากนี้คุณยังสามารถแก้ปัญหานี้โดยการเพิ่มฟังก์ชั่นสมาชิกเช่น:raise() virtual void raise() { throw *this; }อย่าลืมลบล้างข้อยกเว้นในแต่ละข้อยกเว้น
Justin Time - คืนสถานะ Monica

5

ความแตกต่าง: std :: runtime_error vs std :: exception ()

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


3

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

กล่าวอีกนัยหนึ่งก็คือ

catch (...) {cout << "Unknown exception"; }

หรือ

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

และตัวเลือกที่สองดีกว่าแน่นอน


3

หากข้อยกเว้นที่เป็นไปได้ทั้งหมดของคุณได้มาจากการstd::exceptionสกัดกั้นของคุณก็สามารถทำได้catch(std::exception & e)และมั่นใจได้ว่าจะจับทุกอย่างได้

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


7
อย่าคลาสย่อย std :: ข้อยกเว้นแล้วจับ (std :: ข้อยกเว้น e) คุณต้องจับอ้างอิงถึง std :: exception & (หรือตัวชี้) ไม่เช่นนั้นคุณกำลังแบ่งข้อมูลคลาสย่อยออก
jmucchiello

1
ตอนนี้เป็นความผิดพลาดที่โง่ - แน่นอนฉันรู้ดีกว่า stackoverflow.com/questions/1095225/…
Mark Ransom

3

คำถามแรกจะได้มาจากข้อยกเว้นมาตรฐานใด ๆ หรือไม่ การทำเช่นนี้จะเปิดใช้งานตัวจัดการข้อยกเว้นเดียวสำหรับข้อยกเว้นไลบรารีมาตรฐานทั้งหมดและของคุณเอง แต่ยังสนับสนุนตัวจัดการแบบ catch-them-all ปัญหาคือเราควรจับเฉพาะข้อยกเว้นเท่านั้นที่รู้วิธีจัดการ ตัวอย่างเช่นใน main () การจับข้อยกเว้น std :: ทั้งหมดน่าจะเป็นสิ่งที่ดีหากสตริง what () จะถูกบันทึกเป็นทางเลือกสุดท้ายก่อนที่จะออก อย่างไรก็ตามที่อื่นไม่น่าจะเป็นความคิดที่ดี

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


0

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

เช่นข้อยกเว้นร้ายแรงจะยุติโปรแกรมข้อผิดพลาดในการตรวจสอบความถูกต้องจะล้างเฉพาะสแต็กและข้อความค้นหาของผู้ใช้จะถามคำถามจากผู้ใช้ปลายทาง

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


0

แม้ว่าคำถามนี้จะค่อนข้างเก่าและได้รับคำตอบมากมาย แต่ฉันแค่ต้องการเพิ่มหมายเหตุเกี่ยวกับวิธีจัดการข้อยกเว้นที่เหมาะสมใน C ++ 11 เนื่องจากฉันพลาดสิ่งนี้อย่างต่อเนื่องในการอภิปรายเกี่ยวกับข้อยกเว้น:

ใช้std::nested_exceptionและstd::throw_with_nested

มีอธิบายไว้ใน StackOverflow ที่นี่และที่นี่คุณจะได้รับ backtrace ในข้อยกเว้นของคุณอย่างไรภายในโค้ดของคุณโดยไม่ต้องใช้ดีบักเกอร์หรือการบันทึกที่ยุ่งยากเพียงแค่เขียนตัวจัดการข้อยกเว้นที่เหมาะสมซึ่งจะลบข้อยกเว้นที่ซ้อนกันใหม่

เนื่องจากคุณสามารถทำสิ่งนี้กับคลาสข้อยกเว้นที่ได้รับใด ๆ คุณจึงสามารถเพิ่มข้อมูลจำนวนมากลงใน backtrace ดังกล่าวได้! คุณสามารถดูMWEของฉันบน GitHubซึ่ง backtrace จะมีลักษณะดังนี้:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

คุณไม่จำเป็นต้องซับคลาสด้วยซ้ำ std::runtime_errorเพื่อรับข้อมูลมากมายเมื่อมีข้อยกเว้นเกิดขึ้น

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

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.