คำตอบที่น่าสนใจ: แม้ว่าฉันจะเห็นด้วยกับพวกเขาทั้งหมด (จนถึงตอนนี้) แต่ก็มีความหมายแฝงที่เป็นไปได้สำหรับคำถามนี้ซึ่งตอนนี้ถูกมองข้ามไปโดยสิ้นเชิง
หากตัวอย่างง่ายๆด้านบนถูกขยายด้วยการจัดสรรทรัพยากรแล้วเกิดข้อผิดพลาดในการตรวจสอบว่าอาจทำให้ทรัพยากรว่างรูปภาพอาจเปลี่ยนไป
ลองพิจารณาแนวทางที่ไร้เดียงสาของผู้เริ่มต้นอาจใช้:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
ข้างต้นจะแสดงถึงรูปแบบที่รุนแรงของการกลับมาก่อนเวลาอันควร สังเกตว่าโค้ดมีความซ้ำซากจำเจและไม่สามารถบำรุงรักษาได้อย่างไรเมื่อเวลาผ่านไปเมื่อความซับซ้อนเพิ่มขึ้น คนปัจจุบันอาจใช้การจัดการข้อยกเว้นเพื่อจับสิ่งเหล่านี้
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
ฟิลิปแนะนำหลังจากดูตัวอย่าง goto ด้านล่างแล้วเพื่อใช้สวิตช์ / เคสแบบไม่ต้องหยุดพักภายในบล็อกจับด้านบน หนึ่งสามารถสลับ (typeof (จ)) และแล้วตกผ่านfree_resourcex()
สาย แต่นี้ไม่น่ารำคาญและความต้องการการพิจารณาการออกแบบ และจำไว้ว่าสวิตช์ / เคสที่ไม่มีการหยุดพักนั้นเหมือนกับ goto ที่มีป้ายกำกับเดซี่ด้านล่าง ...
ในฐานะที่เป็นมาร์ค B ชี้ให้เห็นใน C ++ ก็ถือว่าเป็นรูปแบบที่ดีที่จะปฏิบัติตามทรัพยากรได้มาคือการเริ่มต้นหลักการRAIIในระยะสั้น สาระสำคัญของแนวคิดคือการใช้การสร้างอินสแตนซ์อ็อบเจ็กต์เพื่อรับทรัพยากร จากนั้นทรัพยากรจะถูกปลดปล่อยโดยอัตโนมัติทันทีที่วัตถุอยู่นอกขอบเขตและมีการเรียกผู้ทำลายล้าง สำหรับทรัพยากรที่อยู่ระหว่างกันจะต้องได้รับการดูแลเป็นพิเศษเพื่อให้แน่ใจว่าลำดับการจัดสรรที่ถูกต้องและการออกแบบประเภทของออบเจ็กต์ที่มีข้อมูลที่จำเป็นสำหรับผู้ทำลายทั้งหมด
หรือในช่วงก่อนวันยกเว้นอาจทำ:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
แต่ตัวอย่างที่ง่ายเกินไปนี้มีข้อบกพร่องหลายประการ: สามารถใช้ได้เฉพาะในกรณีที่ทรัพยากรที่จัดสรรไม่ขึ้นอยู่กับกันและกัน (เช่นไม่สามารถใช้สำหรับการจัดสรรหน่วยความจำจากนั้นเปิดที่จับไฟล์จากนั้นอ่านข้อมูลจากแฮนเดิลลงในหน่วยความจำ ) และไม่ได้ระบุรหัสข้อผิดพลาดที่แยกแยะได้เฉพาะบุคคลเป็นค่าส่งคืน
เพื่อให้โค้ดเร็ว (!) มีขนาดกะทัดรัดและอ่านง่ายและขยายได้Linus Torvalds บังคับใช้รูปแบบที่แตกต่างกันสำหรับโค้ดเคอร์เนลที่เกี่ยวข้องกับทรัพยากรแม้จะใช้goto ที่น่าอับอายในลักษณะที่สมเหตุสมผล :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
สาระสำคัญของการสนทนาในรายการส่งเมลของเคอร์เนลคือคุณลักษณะภาษาส่วนใหญ่ที่ "ต้องการ" เหนือคำสั่ง goto นั้นเป็น gotos โดยปริยายเช่น if / else ขนาดใหญ่ตัวจัดการข้อยกเว้นคำสั่ง loop / break / continue ฯลฯ และ goto ในตัวอย่างข้างต้นถือว่าโอเคเนื่องจากพวกมันกระโดดได้เพียงระยะทางเล็ก ๆ มีป้ายกำกับที่ชัดเจนและไม่มีรหัสของความยุ่งเหยิงอื่น ๆ เพื่อติดตามเงื่อนไขข้อผิดพลาด คำถามนี้ยังได้รับการกล่าวถึงที่นี่ใน StackOverflow
อย่างไรก็ตามสิ่งที่ขาดหายไปในตัวอย่างสุดท้ายเป็นวิธีที่ดีในการส่งคืนรหัสข้อผิดพลาด ฉันกำลังคิดที่จะเพิ่มresult_code++
หลังการfree_resource_x()
โทรแต่ละครั้งและส่งคืนรหัสนั้น แต่สิ่งนี้จะชดเชยความเร็วที่เพิ่มขึ้นของรูปแบบการเข้ารหัสข้างต้น และยากที่จะคืนค่า 0 ในกรณีที่สำเร็จ บางทีฉันอาจจะไม่ได้จินตนาการ ;-)
ใช่ฉันคิดว่ามีความแตกต่างอย่างมากในคำถามของการเข้ารหัสผลตอบแทนก่อนกำหนดหรือไม่ แต่ฉันคิดว่ามันชัดเจนเฉพาะในโค้ดที่ซับซ้อนกว่าซึ่งยากกว่าหรือเป็นไปไม่ได้ที่จะปรับโครงสร้างและปรับให้เหมาะสมสำหรับคอมไพเลอร์ ซึ่งโดยปกติจะเป็นกรณีเมื่อการจัดสรรทรัพยากรเข้ามามีบทบาท