C ++ ดูเหมือนจะชอบการใช้ข้อยกเว้นบ่อยกว่า
ฉันขอแนะนำให้จริงน้อยกว่า Objective-C ในบางประเด็นเพราะไลบรารีมาตรฐาน C ++ จะไม่แสดงข้อผิดพลาดของโปรแกรมเมอร์เช่นการเข้าถึงนอกขอบเขตของลำดับการเข้าถึงแบบสุ่มในรูปแบบการออกแบบเคสที่พบบ่อยที่สุด (ในoperator[]
เช่น) หรือ พยายามที่จะตรวจสอบซ้ำ iterator ที่ไม่ถูกต้อง ภาษาจะไม่ส่งผลต่อการเข้าถึงอาเรย์นอกขอบเขตหรือการยกเลิกตัวชี้โมฆะหรือการเรียงลำดับนี้
การผิดพลาดโปรแกรมเมอร์ส่วนใหญ่ออกจากสมการจัดการข้อยกเว้นจริงจะไปเป็นหมวดหมู่มากของข้อผิดพลาดที่ภาษาอื่น ๆ throwing
มักจะตอบสนองต่อการโดย C ++ มีแนวโน้มที่จะassert
(ซึ่งไม่ได้รับการคอมไพล์ในการวางจำหน่าย / การสร้างการผลิตเพียงแค่การตรวจแก้จุดบกพร่องสร้าง) หรือเพิ่งผิดพลาด (มักจะล้มเหลว) ในกรณีดังกล่าวอาจเป็นส่วนหนึ่งเนื่องจากภาษาไม่ต้องการกำหนดค่าใช้จ่าย ตามที่จะต้องตรวจสอบข้อผิดพลาดโปรแกรมเมอร์ดังกล่าวเว้นแต่โปรแกรมเมอร์ต้องการจ่ายเฉพาะค่าใช้จ่ายโดยการเขียนรหัสที่ดำเนินการตรวจสอบตัวเอง
ซัทเทอร์ยังสนับสนุนให้หลีกเลี่ยงข้อยกเว้นในกรณีเช่นนี้ในมาตรฐานการเข้ารหัส C ++:
ข้อเสียเปรียบหลักของการใช้ข้อยกเว้นเพื่อรายงานข้อผิดพลาดในการเขียนโปรแกรมคือคุณไม่ต้องการให้สแต็คคลี่คลายเกิดขึ้นจริง ๆ เมื่อคุณต้องการให้ดีบักเกอร์เปิดใช้งานในบรรทัดที่แน่นอนซึ่งตรวจพบการละเมิดด้วยสถานะของบรรทัดนั้น โดยสรุป: มีข้อผิดพลาดที่คุณรู้ว่าอาจเกิดขึ้น (ดูรายการ 69 ถึง 75) assert
สำหรับทุกอย่างอื่นที่ไม่ควรและมันเป็นความผิดของโปรแกรมถ้ามันไม่มี
กฎนั้นไม่จำเป็นต้องอยู่ในหิน ในบางกรณีภารกิจที่สำคัญยิ่งกว่านั้นอาจจะดีกว่าที่จะใช้พูดห่อและมาตรฐานการเข้ารหัสที่บันทึกอย่างสม่ำเสมอที่ข้อผิดพลาดของโปรแกรมเมอร์เกิดขึ้นและthrow
ในการปรากฏตัวของข้อผิดพลาดของโปรแกรมเมอร์เช่นพยายามที่จะเชื่อว่าสิ่งที่ไม่ถูกต้อง อาจมีค่าใช้จ่ายสูงเกินกว่าที่จะไม่สามารถกู้คืนได้ในกรณีเหล่านั้นหากซอฟต์แวร์มีโอกาส แต่โดยรวมแล้วการใช้ภาษาที่ใช้กันอย่างแพร่หลายมีแนวโน้มที่จะสนับสนุนไม่ให้เกิดความผิดพลาดของโปรแกรมเมอร์
ข้อยกเว้นภายนอก
ที่ฉันเห็นข้อยกเว้นได้รับการสนับสนุนบ่อยที่สุดใน C ++ (ตามที่คณะกรรมการมาตรฐานเช่น) มีไว้สำหรับ "ข้อยกเว้นภายนอก" ในผลลัพธ์ที่ไม่คาดคิดในบางแหล่งภายนอกนอกโปรแกรม ตัวอย่างไม่สามารถจัดสรรหน่วยความจำได้ อีกประการหนึ่งคือความล้มเหลวในการเปิดไฟล์สำคัญที่จำเป็นสำหรับซอฟต์แวร์ที่จะเรียกใช้ อีกประการหนึ่งไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ที่ต้องการ อีกประการหนึ่งคือผู้ใช้ติดขัดปุ่มยกเลิกเพื่อยกเลิกการดำเนินการที่มีเส้นทางการดำเนินการกรณีที่พบบ่อยคาดว่าจะประสบความสำเร็จในการขาดการหยุดชะงักภายนอกนี้ ทุกสิ่งเหล่านี้อยู่นอกเหนือการควบคุมของซอฟต์แวร์ทันทีและโปรแกรมเมอร์ผู้เขียนมัน พวกเขาเป็นผลลัพธ์ที่ไม่คาดคิดจากแหล่งข้อมูลภายนอกที่ป้องกันการดำเนินการ (ซึ่งควรจะคิดว่าเป็นการทำธุรกรรมที่แยกไม่ได้ในหนังสือของฉัน *) จากการประสบความสำเร็จ
การทำธุรกรรม
ฉันมักจะสนับสนุนให้ดูtry
บล็อกเป็น "ธุรกรรม" เพราะธุรกรรมควรประสบความสำเร็จโดยรวมหรือล้มเหลวโดยรวม หากเราพยายามทำอะไรบางอย่างและล้มเหลวไปครึ่งทางแล้วผลข้างเคียงใด ๆ / การกลายพันธุ์ที่เกิดขึ้นกับสถานะของโปรแกรมโดยทั่วไปจะต้องย้อนกลับเพื่อทำให้ระบบกลับสู่สถานะที่ถูกต้องราวกับว่าธุรกรรมนั้นไม่เคยดำเนินการเลย เช่นเดียวกับ RDBMS ซึ่งไม่สามารถประมวลผลแบบสอบถามได้ครึ่งทางไม่ควรทำให้ความสมบูรณ์ของฐานข้อมูลลดลง หากคุณเปลี่ยนสถานะโปรแกรมโดยตรงในธุรกรรมดังกล่าวคุณจะต้อง "เลิกทำ" เมื่อพบข้อผิดพลาด
ทางเลือกที่ง่ายกว่ามากคือไม่ทำให้เกิดการเปลี่ยนแปลงสถานะโปรแกรมดั้งเดิม คุณอาจกลายพันธุ์สำเนาของมันและจากนั้นหากมันประสบความสำเร็จให้สลับสำเนากับต้นฉบับ (ทำให้แน่ใจว่าไม่สามารถสลับได้) ถ้ามันล้มเหลวทิ้งสำเนา สิ่งนี้ยังใช้แม้ว่าคุณจะไม่ได้ใช้ข้อยกเว้นสำหรับการจัดการข้อผิดพลาดโดยทั่วไป ความคิด "การทำธุรกรรม" เป็นกุญแจสำคัญในการกู้คืนที่เหมาะสมหากการกลายพันธุ์สถานะของโปรแกรมเกิดขึ้นก่อนที่จะพบข้อผิดพลาด มันประสบความสำเร็จโดยรวมหรือล้มเหลวโดยรวม มันไม่ประสบความสำเร็จในการสร้างการกลายพันธุ์
นี่เป็นหนึ่งในหัวข้อที่ถูกกล่าวถึงอย่างน้อยหนึ่งครั้งเมื่อฉันเห็นโปรแกรมเมอร์ถามถึงวิธีการจัดการข้อผิดพลาดหรือข้อยกเว้นอย่างถูกต้อง แต่มันก็เป็นเรื่องที่ยากที่สุดสำหรับพวกเขาที่จะได้รับสิทธิในซอฟต์แวร์ใด ๆ การดำเนินงานของมัน ความบริสุทธิ์และการเปลี่ยนแปลงไม่ได้สามารถช่วยที่นี่เพื่อให้เกิดข้อยกเว้นด้านความปลอดภัยเช่นเดียวกับที่ช่วยในเรื่องความปลอดภัยของด้ายในฐานะที่เป็นผลข้างเคียงของการกลายพันธุ์ / ภายนอกที่ไม่เกิดขึ้นไม่จำเป็นต้องย้อนกลับ
ประสิทธิภาพ
อีกปัจจัยที่ชี้แนะว่าการใช้ข้อยกเว้นคือประสิทธิภาพหรือไม่และฉันไม่ได้หมายถึงการครอบงำทางการเงิน คอมไพเลอร์ C ++ จำนวนมากใช้สิ่งที่เรียกว่า "การจัดการข้อยกเว้นศูนย์ต้นทุน"
มันไม่มีค่าใช้จ่ายรันไทม์ศูนย์สำหรับการดำเนินการปราศจากข้อผิดพลาดซึ่งเกินกว่าการจัดการข้อผิดพลาดค่าตอบแทน C การแลกเปลี่ยนข้อยกเว้นมีค่าใช้จ่ายสูง
จากสิ่งที่ฉันได้อ่านเกี่ยวกับมันทำให้พา ธ การประมวลผลเคสของคุณไม่จำเป็นต้องมีค่าใช้จ่าย (แม้แต่ค่าใช้จ่ายที่ปกติมาพร้อมกับการจัดการและการเผยแพร่โค้ดข้อผิดพลาดสไตล์ C) เพื่อแลกกับค่าใช้จ่ายที่แพง ซึ่งหมายความว่าthrowing
ตอนนี้มีราคาแพงกว่าที่เคย)
"แพง" นั้นค่อนข้างยากที่จะหาปริมาณ แต่สำหรับผู้เริ่มต้นคุณอาจไม่ต้องการที่จะขว้างเป็นล้าน ๆ ครั้งในวงแคบ ๆ การออกแบบประเภทนี้ถือว่าข้อยกเว้นไม่เกิดขึ้นทางซ้ายและขวาตลอดเวลา
ที่ไม่ใช่ข้อผิดพลาด
และจุดแสดงนั้นทำให้ฉันมีข้อผิดพลาดซึ่งไม่น่าแปลกใจหากเราดูภาษาอื่น ๆ ทุกประเภท แต่ฉันจะบอกว่าเนื่องจากการออกแบบ EH ที่ไม่มีต้นทุนดังกล่าวข้างต้นคุณเกือบจะไม่ต้องการthrow
ตอบสนองต่อกุญแจที่ไม่พบในชุด เพราะไม่เพียง แต่เป็นสิ่งที่ไม่ใช่ข้อผิดพลาดเท่านั้น (ผู้ที่ค้นหากุญแจอาจสร้างชุดและคาดว่าจะค้นหากุญแจที่ไม่มีอยู่เสมอ) แต่มันจะมีราคาแพงอย่างมากในบริบทนั้น
ตัวอย่างเช่นฟังก์ชั่นการแยกชุดอาจต้องการวนซ้ำสองชุดและค้นหาคีย์ที่มีเหมือนกัน หากล้มเหลวในการค้นหากุญแจthrew
คุณจะวนซ้ำและอาจพบข้อยกเว้นในการทำซ้ำครึ่งหรือมากกว่า:
Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
Set<int> intersection;
for (int key: a)
{
try
{
b.find(key);
intersection.insert(other_key);
}
catch (const KeyNotFoundException&)
{
// Do nothing.
}
}
return intersection;
}
ตัวอย่างข้างบนนั้นไร้สาระและโอ้อวดจริง ๆ แต่ฉันได้เห็นในรหัสการผลิตบางคนมาจากภาษาอื่นที่ใช้ข้อยกเว้นใน C ++ ค่อนข้างเช่นนี้และฉันคิดว่ามันเป็นคำสั่งที่ใช้งานได้จริงว่านี่ไม่ใช่การใช้ข้อยกเว้นที่เหมาะสม ใน C ++ คำใบ้อีกข้อข้างต้นก็คือคุณจะสังเกตเห็นว่าcatch
บล็อกนั้นไม่มีอะไรที่ต้องทำและถูกเขียนขึ้นเพื่อเพิกเฉยต่อข้อยกเว้นเช่นนี้และโดยทั่วไปจะเป็นคำใบ้ (แม้ว่าจะไม่ใช่ผู้ค้ำประกัน) ว่าข้อยกเว้นนั้นอาจไม่ถูกใช้อย่างเหมาะสมใน C ++
สำหรับประเภทของกรณีเหล่านั้นบางประเภทของค่าตอบแทนที่บ่งบอกถึงความล้มเหลว (อะไรจากการกลับfalse
ไปที่ iterator ที่ไม่ถูกต้องหรือnullptr
หรือสิ่งที่ทำให้รู้สึกในบริบท) มักจะเหมาะสมกว่าและมักจะเป็นประโยชน์และมีประสิทธิผลมากขึ้น ตัวพิมพ์เล็กและใหญ่มักจะไม่เรียกใช้กระบวนการคลายสแต็กเพื่อเข้าถึงcatch
ไซต์แบบอะนาล็อก
คำถาม
ฉันจะต้องไปกับการตั้งค่าสถานะข้อผิดพลาดภายในถ้าฉันเลือกที่จะหลีกเลี่ยงข้อยกเว้น มันจะลำบากเกินกว่าจะจัดการหรืออาจจะทำงานได้ดีกว่าข้อยกเว้น? การเปรียบเทียบทั้งสองกรณีจะเป็นคำตอบที่ดีที่สุด
การหลีกเลี่ยงข้อยกเว้นอย่างสมบูรณ์ใน C ++ ดูเหมือนว่าจะต่อต้านฉันได้มากยกเว้นว่าคุณกำลังทำงานในระบบฝังตัวบางตัวหรือบางประเภทที่ห้ามการใช้งานของพวกเขา (ในกรณีนี้คุณต้องหลีกทางให้พ้นด้วย ฟังก์ชั่นห้องสมุดและภาษาที่throw
ต้องการใช้อย่างเคร่งครัดnothrow
new
)
ถ้าคุณอย่างต้องหลีกเลี่ยงข้อยกเว้นด้วยเหตุผลใดก็ตาม (เช่นทำงานใน C ขอบเขต API ของโมดูลที่มี API C คุณส่งออก) หลายคนอาจจะไม่เห็นด้วยกับฉัน แต่ฉันต้องการจริงขอแนะนำให้ใช้ข้อผิดพลาดระดับโลกจัดการ / สถานะเช่น OpenGL glGetError()
กับ คุณสามารถทำให้มันใช้หน่วยเก็บข้อมูลเธรดโลคัลเพื่อมีสถานะข้อผิดพลาดเฉพาะต่อเธรด
เหตุผลของฉันคือฉันไม่คุ้นเคยกับการเห็นทีมในสภาพแวดล้อมการผลิตตรวจสอบข้อผิดพลาดที่เป็นไปได้ทั้งหมด แต่น่าเสียดายที่เมื่อส่งคืนรหัสข้อผิดพลาด หากพวกเขาละเอียดถี่ถ้วน C API บางตัวอาจพบข้อผิดพลาดเกี่ยวกับการเรียก C API ทุกครั้งและการตรวจสอบอย่างละเอียดจะต้องมีสิ่งต่อไปนี้:
if ((err = ApiCall(...)) != success)
{
// Handle error
}
... ด้วยเกือบทุกบรรทัดของรหัสเรียก API ที่ต้องการตรวจสอบดังกล่าว แต่ฉันไม่ได้มีโชคลาภในการทำงานกับทีมที่ละเอียด พวกเขามักเพิกเฉยต่อข้อผิดพลาดดังกล่าวครึ่งหนึ่งและบางครั้งก็เป็นส่วนใหญ่ นั่นคือการอุทธรณ์ที่ยิ่งใหญ่ที่สุดสำหรับฉันยกเว้น หากเราสรุป API นี้และทำให้throw
เป็นข้อผิดพลาดอย่างสม่ำเสมอไม่สามารถละเว้นได้และในมุมมองและประสบการณ์ของฉันนั่นคือสิ่งที่เหนือกว่าของข้อยกเว้นอยู่
แต่หากไม่สามารถใช้การยกเว้นได้สถานะข้อผิดพลาดระดับโลกต่อเธรดอย่างน้อยจะมีข้อได้เปรียบ (อันยิ่งใหญ่เมื่อเทียบกับการส่งคืนรหัสข้อผิดพลาดให้ฉัน) ว่าอาจมีโอกาสได้รับข้อผิดพลาดเดิมช้ากว่าเมื่อมัน เกิดขึ้นในบาง codebase เลอะเทอะแทนที่จะหายไปทันทีและปล่อยให้เราหลงลืมอย่างสมบูรณ์เกี่ยวกับสิ่งที่เกิดขึ้น ข้อผิดพลาดอาจเกิดขึ้นสองสามบรรทัดก่อนหน้าหรือในการเรียกใช้ฟังก์ชันก่อนหน้านี้ แต่หากซอฟต์แวร์ยังไม่ทำงานล้มเหลวเราอาจเริ่มทำงานย้อนกลับและหาสาเหตุและสาเหตุได้
สำหรับฉันแล้วดูเหมือนว่าเนื่องจากพอยน์เตอร์หายากฉันจะต้องดำเนินการกับข้อผิดพลาดภายในหากฉันเลือกที่จะหลีกเลี่ยงข้อยกเว้น
ฉันไม่จำเป็นต้องพูดว่าพอยน์เตอร์นั้นหายาก แม้จะมีวิธีการใน C ++ 11 ขึ้นไปเพื่อรับข้อมูลพอยน์เตอร์ของคอนเทนเนอร์และnullptr
คำสำคัญใหม่ โดยทั่วไปถือว่าไม่ฉลาดที่จะใช้ตัวชี้แบบ raw เพื่อเป็นเจ้าของ / จัดการหน่วยความจำหากคุณสามารถใช้บางอย่างเช่นunique_ptr
แทนที่จะให้ความสำคัญกับการเป็น RAII ที่สอดคล้องกับสถานะของข้อยกเว้น แต่พอยน์เตอร์ดิบที่ไม่ได้เป็นเจ้าของ / จัดการหน่วยความจำก็ไม่ได้ถือว่าแย่มาก (แม้แต่จากคนอย่างซัทเทอร์และสตราวสตรัป) และบางครั้งก็ใช้งานได้จริงเพื่อชี้ไปยังสิ่งต่าง ๆ
เนื้อหาเหล่านั้นปลอดภัยไม่น้อยไปกว่าตัวทำซ้ำคอนเทนเนอร์มาตรฐาน (อย่างน้อยในรุ่นที่วางจำหน่ายขาดตัวตรวจสอบตัวทำซ้ำ) ซึ่งจะไม่ตรวจพบว่าคุณพยายามตรวจสอบซ้ำหลังจากที่พวกเขาถูกทำให้เป็นโมฆะ ภาษา C ++ ยังคงเป็นภาษาที่อันตรายอยู่บ้างผมจะบอกว่าถ้าคุณใช้มันโดยเฉพาะเพื่อต้องการห่อทุกอย่างและซ่อนแม้กระทั่งตัวชี้ดิบที่ไม่ได้เป็นเจ้าของ เกือบเป็นเรื่องสำคัญยิ่งที่มีข้อยกเว้นว่าทรัพยากรเป็นไปตาม RAII (ซึ่งโดยทั่วไปจะไม่มีค่าใช้จ่ายในการดำเนินการ) แต่นอกเหนือจากนั้นไม่จำเป็นต้องพยายามใช้ภาษาที่ปลอดภัยที่สุดเพื่อหลีกเลี่ยงค่าใช้จ่ายที่นักพัฒนาไม่ต้องการ แลกกับสิ่งอื่น การใช้ที่แนะนำไม่ได้พยายามปกป้องคุณจากสิ่งต่าง ๆ เช่นตัวชี้ห้อยและตัววนซ้ำที่ใช้งานไม่ได้ดังนั้นจึงควรพูด (ไม่เช่นนั้นเราควรสนับสนุนให้ใช้shared_ptr
ทั่วทุกสถานที่ซึ่ง Stroustrup ร้อนแรงคัดค้าน) มันพยายามที่จะปกป้องคุณจากความล้มเหลวที่จะปล่อย / ถูกต้องฟรี / ทำลาย / ปลด / throws
ทำความสะอาดทรัพยากรเมื่อบางสิ่งบางอย่าง