มีหลายวิธี แต่แรกที่คุณต้องเข้าใจว่าทำไมการล้างวัตถุเป็นสิ่งสำคัญและด้วยเหตุนี้เหตุผลที่std::exit
เป็นชายขอบในหมู่โปรแกรมเมอร์ C ++
ไร่และคลี่คลายคลี่คลาย
C ++ ใช้ประโยชน์จากสำนวนที่เรียกว่าRAIIซึ่งในแง่ง่ายๆหมายความว่าวัตถุควรทำการเริ่มต้นในตัวสร้างและการล้างข้อมูลใน destructor ตัวอย่างเช่นstd::ofstream
คลาส [อาจ] เปิดไฟล์ในระหว่างตัวสร้างจากนั้นผู้ใช้ทำการดำเนินการกับมันและในที่สุดในตอนท้ายของวงจรชีวิตของมันมักจะถูกกำหนดโดยขอบเขตของมัน destructor ที่เรียกว่าปิดไฟล์และวูบวาบ เนื้อหาที่เป็นลายลักษณ์อักษรใด ๆ ลงในดิสก์
จะเกิดอะไรขึ้นถ้าคุณไม่ไปที่ destructor เพื่อล้างข้อมูลและปิดไฟล์ ใครจะรู้! แต่อาจจะไม่เขียนข้อมูลทั้งหมดที่ควรจะเขียนลงในไฟล์
เช่นพิจารณารหัสนี้
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
เกิดอะไรขึ้นในแต่ละความเป็นไปได้คือ:
- ความเป็นไปได้ที่ 1: การส่งคืนจะออกจากขอบเขตของฟังก์ชันปัจจุบันดังนั้นจึงทราบเกี่ยวกับจุดสิ้นสุดของวงจรชีวิตของการ
os
เรียกใช้ destructor และทำการล้างข้อมูลที่เหมาะสมโดยการปิดและล้างแฟ้มไปยังดิสก์
- ความเป็นไปได้ที่ 2: การโยนข้อยกเว้นยังช่วยดูแลวงจรชีวิตของวัตถุในขอบเขตปัจจุบันด้วยดังนั้นการทำความสะอาดที่เหมาะสม ...
- ความเป็นไปได้ที่ 3: ที่นี่สแต็คคลี่คลายเข้าสู่การกระทำ! แม้ว่าข้อยกเว้นจะถูกโยนทิ้งที่
inner_mad
, unwinder จะไปแม้ว่าสแต็คของmad
และmain
จะดำเนินการล้างที่เหมาะสมวัตถุทั้งหมดจะได้รับการ destructed อย่างถูกต้องรวมทั้งและptr
os
- ความเป็นไปได้ที่ 4:นี่ไง
exit
เป็นฟังก์ชั่น C และไม่ทราบหรือไม่เข้ากันกับสำนวน C ++ มันไม่ได้ทำการล้างข้อมูลบนวัตถุของคุณรวมถึงos
ในขอบเขตเดียวกัน ดังนั้นไฟล์ของคุณจะไม่ถูกปิดอย่างถูกต้องและด้วยเหตุนี้เนื้อหาจึงอาจไม่ถูกเขียนลงไป!
- ความเป็นไปได้อื่น ๆ :มันจะออกจากขอบเขตหลักโดยการดำเนินการโดยปริยาย
return 0
และทำให้มีผลเช่นเดียวกับความเป็นไปได้ 1 เช่นการล้างที่เหมาะสม
แต่ไม่แน่ใจเกี่ยวกับสิ่งที่ฉันเพิ่งบอกคุณ (ส่วนใหญ่เป็นไปได้ที่ 2 และ 3); อ่านต่อและเราจะค้นหาวิธีดำเนินการล้างข้อมูลตามข้อยกเว้นที่เหมาะสม
วิธีที่เป็นไปได้เพื่อยุติ
กลับจากหลัก!
คุณควรทำเช่นนี้ทุกครั้งที่ทำได้ ต้องการกลับจากโปรแกรมของคุณเสมอโดยส่งคืนสถานะทางออกที่เหมาะสมจากหลัก
ผู้เรียกโปรแกรมของคุณและอาจเป็นระบบปฏิบัติการอาจต้องการทราบว่าสิ่งที่โปรแกรมของคุณควรทำเสร็จเรียบร้อยหรือไม่ ด้วยเหตุผลเดียวกันนี้คุณควรส่งคืนค่าศูนย์หรือEXIT_SUCCESS
เพื่อส่งสัญญาณว่าโปรแกรมสิ้นสุดลงอย่างสมบูรณ์และEXIT_FAILURE
เพื่อส่งสัญญาณว่าโปรแกรมหยุดทำงานไม่สำเร็จรูปแบบอื่น ๆ ของค่าตอบแทนจะถูกกำหนดโดยการนำไปใช้ ( §18.5 / 8 )
อย่างไรก็ตามคุณอาจจะลึกมากในสแต็คการโทรและการคืนทั้งหมดอาจเจ็บปวด ...
[อย่า] โยนข้อยกเว้น
การโยนข้อยกเว้นจะทำการล้างวัตถุที่เหมาะสมโดยใช้การคลี่คลายคลี่คลายโดยการเรียก destructor ของทุกวัตถุในขอบเขตก่อนหน้าใด ๆ
แต่นี่คือการจับ ! มันถูกกำหนดให้มีการนำไปใช้งานไม่ว่าจะทำการคลี่คลายสแต็กหรือไม่เมื่อข้อยกเว้นที่ส่งออกมานั้นไม่ได้รับการจัดการ(โดยประโยค catch (... ))หรือแม้ว่าคุณจะมีnoexcept
ฟังก์ชั่นอยู่ตรงกลางของ call stack นี่คือที่ระบุไว้ใน§15.5.1 [ยกเว้น.terminate] :
ในบางสถานการณ์การจัดการข้อยกเว้นต้องถูกทอดทิ้งสำหรับเทคนิคการจัดการข้อผิดพลาดที่ละเอียดน้อยกว่า [หมายเหตุ: สถานการณ์เหล่านี้คือ:
[ ... ]
- เมื่อกลไกการจัดการข้อยกเว้นไม่สามารถหาตัวจัดการสำหรับข้อยกเว้นโยน (15.3) หรือเมื่อการค้นหาตัวจัดการ (15.3) พบบล็อกด้านนอกสุดของฟังก์ชั่นที่มีnoexcept
-specificationที่ไม่อนุญาตข้อยกเว้น (15.4) หรือ [ ... ]
[ ... ]
ในกรณีเช่นนี้ std :: terminate () ถูกเรียก (18.8.3) ในสถานการณ์ที่ไม่พบตัวจัดการที่ตรงกันจะมีการกำหนดการนำไปใช้หรือไม่สแต็กไม่ถูกคลายออกก่อนที่ std :: terminate () จะถูกเรียกว่า [... ]
ดังนั้นเราต้องจับมัน!
อย่าโยนข้อยกเว้นและจับมันเป็นหลัก!
เนื่องจากข้อยกเว้นที่ไม่ถูกตรวจจับอาจไม่ทำการคลายสแต็ก(และดังนั้นจะไม่ทำการล้างข้อมูลที่เหมาะสม)เราควรตรวจจับข้อยกเว้นในหลักแล้วจึงกลับสถานะการออก ( EXIT_SUCCESS
หรือEXIT_FAILURE
)
ดังนั้นการตั้งค่าที่ดีน่าจะเป็น:
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
[อย่า] std :: exit
สิ่งนี้ไม่ทำการคลี่คลายการเรียงลำดับใด ๆ และไม่มีวัตถุใดที่มีชีวิตบนสแต็กจะเรียก destructor ที่เกี่ยวข้องเพื่อทำการล้างข้อมูล
สิ่งนี้ถูกบังคับใช้ใน§3.6.1 / 4 [basic.start.init] :
ยกเลิกโปรแกรมโดยไม่ออกจากบล็อกปัจจุบัน (เช่นโดยการเรียกฟังก์ชั่นมาตรฐาน :: ออก (int) (18.5)) ไม่ทำลายวัตถุใด ๆ ที่มีระยะเวลาการจัดเก็บข้อมูลอัตโนมัติ (12.4) ถ้า std :: exit ถูกเรียกให้จบโปรแกรมในระหว่างการทำลายวัตถุที่มีระยะเวลาคงที่หรือการจัดเก็บด้ายโปรแกรมมีพฤติกรรมที่ไม่ได้กำหนด
ลองคิดดูสิทำไมคุณจะทำสิ่งนี้ คุณมีวัตถุกี่ชิ้นที่ได้รับความเสียหายอย่างเจ็บปวด?
ทางเลือกอื่น [ไม่ดี]
มีวิธีอื่นในการยกเลิกโปรแกรม(นอกเหนือจากการหยุดทำงาน)แต่ไม่แนะนำให้ทำ เพียงเพื่อความกระจ่างพวกเขาจะถูกนำเสนอที่นี่ ขอให้สังเกตว่าการยกเลิกโปรแกรมปกติ ไม่ได้หมายถึงการคลี่คลายสแต็ก แต่เป็นเรื่องปกติสำหรับระบบปฏิบัติการ
std::_Exit
ทำให้โปรแกรมหยุดทำงานตามปกติและนั่นก็คือ
std::quick_exit
ทำให้การยกเลิกโปรแกรมปกติและstd::at_quick_exit
ตัวจัดการการโทรไม่มีการดำเนินการล้างข้อมูลอื่น
std::exit
ทำให้โปรแกรมหยุดทำงานปกติแล้วเรียกเครื่องมือstd::atexit
จัดการ การล้างข้อมูลประเภทอื่น ๆ จะดำเนินการเช่นการเรียก destructors วัตถุคงที่
std::abort
ทำให้โปรแกรมหยุดทำงานผิดปกติไม่มีการล้างข้อมูล สิ่งนี้ควรถูกเรียกใช้หากโปรแกรมสิ้นสุดลงด้วยวิธีที่ไม่คาดคิดจริงๆ มันจะไม่ทำอะไรเลยนอกจากส่งสัญญาณระบบปฏิบัติการเกี่ยวกับการเลิกจ้างที่ผิดปกติ บางระบบดำเนินการถ่ายโอนข้อมูลหลักในกรณีนี้
std::terminate
โทรstd::terminate_handler
ที่โทรstd::abort
ตามค่าเริ่มต้น
main()
ใช้งานคืนในฟังก์ชั่นใช้ค่าตอบแทนที่เหมาะสมหรือโยนข้อยกเว้นที่เหมาะสม ห้ามใช้exit()
!