Có một số cách, nhưng trước tiên bạn cần hiểu tại sao việc dọn dẹp đối tượng lại quan trọng và do đó lý do std::exit
bị gạt ra ngoài lề giữa các lập trình viên C ++.
RAII và Stack Unwinding
C ++ sử dụng một thành ngữ gọi là RAII , trong thuật ngữ đơn giản có nghĩa là các đối tượng nên thực hiện khởi tạo trong hàm tạo và dọn dẹp trong hàm hủy. Ví dụ, std::ofstream
lớp [có thể] mở tệp trong hàm tạo, sau đó người dùng thực hiện các thao tác đầu ra trên nó và cuối cùng ở cuối vòng đời của nó, thường được xác định bởi phạm vi của nó, hàm hủy được gọi là về cơ bản đóng tệp và xả bất kỳ nội dung bằng văn bản vào đĩa.
Điều gì xảy ra nếu bạn không truy cập vào hàm hủy để xóa và đóng tệp? Ai biết! Nhưng có lẽ nó sẽ không ghi tất cả dữ liệu mà nó được cho là ghi vào tệp.
Ví dụ, hãy xem xét mã này
#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);
}
Điều gì xảy ra trong mỗi khả năng là:
- Khả năng 1: Trả về cơ bản rời khỏi phạm vi chức năng hiện tại, vì vậy nó biết về sự kết thúc của vòng đời
os
do đó gọi hàm hủy của nó và thực hiện dọn dẹp đúng cách bằng cách đóng và xả tệp vào đĩa.
- Khả năng 2: Ném một ngoại lệ cũng đảm nhiệm vòng đời của các đối tượng trong phạm vi hiện tại, do đó thực hiện dọn dẹp đúng cách ...
- Khả năng 3: Ở đây stack uninding đi vào hành động! Mặc dù ngoại lệ được ném vào
inner_mad
, trình duyệt sẽ chuyển qua ngăn xếp mad
và main
để thực hiện dọn dẹp đúng cách, tất cả các đối tượng sẽ bị phá hủy đúng cách, bao gồm ptr
và os
.
- Khả năng 4: Vâng, đây?
exit
là một hàm C và nó không nhận biết cũng không tương thích với các thành ngữ C ++. Nó không thực hiện dọn dẹp trên các đối tượng của bạn, kể cả os
trong cùng một phạm vi. Vì vậy, tệp của bạn sẽ không được đóng đúng cách và vì lý do này, nội dung có thể không bao giờ được ghi vào đó!
- Các khả năng khác: Nó sẽ chỉ để lại phạm vi chính, bằng cách thực hiện một ẩn
return 0
và do đó có tác dụng tương tự như khả năng 1, tức là dọn dẹp đúng cách.
Nhưng đừng quá chắc chắn về những gì tôi vừa nói với bạn (chủ yếu là khả năng 2 và 3); tiếp tục đọc và chúng tôi sẽ tìm hiểu làm thế nào để thực hiện dọn dẹp dựa trên ngoại lệ thích hợp.
Những cách có thể kết thúc
Trở về từ chính!
Bạn nên làm điều này bất cứ khi nào có thể; luôn luôn thích quay lại chương trình của bạn bằng cách trả về trạng thái thoát thích hợp từ chính.
Người gọi chương trình của bạn, và có thể là hệ điều hành, có thể muốn biết liệu chương trình của bạn được thực hiện có thành công hay không. Vì lý do tương tự, bạn nên trả về 0 hoặc EXIT_SUCCESS
để báo hiệu rằng chương trình đã kết thúc thành công và EXIT_FAILURE
để báo hiệu chương trình kết thúc không thành công, bất kỳ hình thức giá trị trả về nào khác đều được xác định theo thực hiện ( §18.5 / 8 ).
Tuy nhiên, bạn có thể rất sâu trong ngăn xếp cuộc gọi và trả lại tất cả số đó có thể gây đau đớn ...
[Đừng] ném ngoại lệ
Ném một ngoại lệ sẽ thực hiện dọn dẹp đối tượng thích hợp bằng cách sử dụng ngăn xếp ngăn xếp, bằng cách gọi hàm hủy của mọi đối tượng trong bất kỳ phạm vi nào trước đó.
Nhưng đây là cái bẫy ! Nó được xác định theo thực thi cho dù việc hủy xếp chồng được thực hiện khi một ngoại lệ bị ném không được xử lý (bởi mệnh đề Catch (...)) hoặc ngay cả khi bạn có một noexcept
hàm ở giữa ngăn xếp cuộc gọi. Điều này được nêu trong §15.5.1 [trừ.terminate] :
Trong một số tình huống xử lý ngoại lệ phải được bỏ qua cho các kỹ thuật xử lý lỗi kém tinh tế. [Lưu ý: Những tình huống này là:
[...]
- khi cơ chế xử lý ngoại lệ không thể tìm thấy một handler cho một ngoại lệ ném (15.3), hoặc khi việc tìm kiếm một handler (15.3) gặp khối ngoài cùng của một hàm với một noexcept
-specification rằng không cho phép ngoại lệ (15,4), hoặc [...]
[...]
Trong các trường hợp như vậy, std :: terminating () được gọi là (18.8.3). Trong trường hợp không tìm thấy trình xử lý phù hợp, nó được xác định theo thực thi cho dù ngăn xếp có bị hủy hay không trước khi std :: terminating () được gọi là [...]
Vì vậy, chúng ta phải bắt nó!
Đừng ném một ngoại lệ và bắt nó ở chính!
Vì các trường hợp ngoại lệ chưa được thực hiện có thể không thực hiện hủy bỏ ngăn xếp (và do đó sẽ không thực hiện dọn dẹp đúng cách) , chúng ta nên bắt ngoại lệ trong chính và sau đó trả về trạng thái thoát ( EXIT_SUCCESS
hoặc EXIT_FAILURE
).
Vì vậy, một thiết lập có thể tốt sẽ là:
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;
}
/* ... */
}
[Đừng] std :: thoát
Điều này không thực hiện bất kỳ loại ngăn xếp ngăn xếp nào và không có đối tượng còn sống nào trên ngăn xếp sẽ gọi hàm hủy tương ứng của nó để thực hiện dọn dẹp.
Điều này được thi hành trong §3.6.1 / 4 [basic.start.init] :
Việc kết thúc chương trình mà không rời khỏi khối hiện tại (ví dụ: bằng cách gọi hàm std :: exit (int) (18.5)) không phá hủy bất kỳ đối tượng nào có thời gian lưu trữ tự động (12.4) . Nếu std :: exit được gọi để kết thúc chương trình trong quá trình hủy đối tượng có thời gian lưu trữ tĩnh hoặc luồng, chương trình có hành vi không xác định.
Hãy suy nghĩ về nó bây giờ, tại sao bạn sẽ làm một điều như vậy? Có bao nhiêu đồ vật đã làm bạn đau đớn?
Các lựa chọn thay thế [xấu khác]
Có nhiều cách khác để chấm dứt chương trình (trừ sự cố) , nhưng chúng không được khuyến nghị. Chỉ để làm rõ, họ sẽ được trình bày ở đây. Lưu ý cách kết thúc chương trình bình thường không có nghĩa là ngăn xếp ngăn xếp mà là trạng thái ổn cho hệ điều hành.
std::_Exit
gây ra chấm dứt chương trình bình thường, và đó là nó.
std::quick_exit
gây ra chấm dứt chương trình bình thường và gọi std::at_quick_exit
trình xử lý, không thực hiện dọn dẹp nào khác.
std::exit
gây ra một chấm dứt chương trình bình thường và sau đó gọi std::atexit
xử lý. Các loại dọn dẹp khác được thực hiện như gọi các hàm hủy đối tượng tĩnh.
std::abort
gây ra một chấm dứt chương trình bất thường, không có dọn dẹp được thực hiện. Điều này nên được gọi nếu chương trình kết thúc một cách thực sự, thực sự bất ngờ. Nó sẽ không làm gì ngoài việc báo hiệu cho HĐH về việc chấm dứt bất thường. Một số hệ thống thực hiện một bãi chứa lõi trong trường hợp này.
std::terminate
gọi các std::terminate_handler
cuộc gọi std::abort
theo mặc định.
main()
trả về sử dụng, trong các hàm sử dụng một giá trị trả về thích hợp hoặc ném Ngoại lệ thích hợp. Đừng dùngexit()
!