Thứ nhất, như những người khác đã nói, mọi thứ không mà rõ ràng trong C ++, IMHO chủ yếu là vì các yêu cầu và hạn chế được phần nào nhiều thay đổi trong C ++ so với các ngôn ngữ khác, đặc biệt. C # và Java, có các vấn đề ngoại lệ "tương tự".
Tôi sẽ phơi bày trên ví dụ std :: stof:
chuyển một chuỗi rỗng đến std :: stof (sẽ ném không hợp lệ) không phải là lỗi lập trình
Hợp đồng cơ bản , như tôi thấy, của chức năng này là nó cố gắng chuyển đổi đối số của nó thành một số float và bất kỳ lỗi nào không được thực hiện đều được báo cáo bởi một ngoại lệ. Cả hai trường hợp ngoại lệ có thể được bắt nguồn từ logic_error
nhưng không phải theo nghĩa của lỗi lập trình viên mà theo nghĩa "đầu vào không thể, bao giờ, được chuyển đổi thành dấu phẩy".
Ở đây, người ta có thể nói a logic_error
được sử dụng để chỉ ra rằng, với đầu vào (thời gian chạy), luôn cố gắng chuyển đổi nó - nhưng đó là công việc của hàm để xác định điều đó và cho bạn biết (thông qua ngoại lệ).
Lưu ý bên lề: Theo quan điểm đó, một cái runtime_error
có thể được xem như một cái gì đó, được đưa ra cùng một đầu vào cho một chức năng, về mặt lý thuyết có thể thành công cho các lần chạy khác nhau. (ví dụ: thao tác tệp, truy cập DB, v.v.)
Lưu ý bên lề: Thư viện regex C ++ đã chọn xuất phát lỗi từ runtime_error
mặc dù có những trường hợp có thể được phân loại giống như ở đây (mẫu regex không hợp lệ).
Điều này chỉ cho thấy, IMHO, việc nhóm logic_
hoặc runtime_
lỗi khá mờ trong C ++ và không thực sự giúp ích nhiều trong trường hợp chung (*) - nếu bạn cần xử lý các lỗi cụ thể, có lẽ bạn cần phải bắt thấp hơn hai.
(*): Đó không phải là để nói rằng một mảnh duy nhất của mã không phải nhất quán, nhưng cho dù bạn ném runtime_
hoặc logic_
hoặc custom_
somethings thực sự không phải là quan trọng, tôi nghĩ.
Để bình luận về cả hai stof
và bitset
:
Cả hai hàm đều lấy các chuỗi làm đối số và trong cả hai trường hợp, đó là:
- không tầm thường để kiểm tra người gọi xem một chuỗi đã cho có hợp lệ không (ví dụ trường hợp xấu nhất bạn phải sao chép logic hàm; trong trường hợp bitet, không rõ ngay lập tức chuỗi rỗng có hợp lệ hay không, vì vậy hãy để ctor quyết định)
- Hàm đã có trách nhiệm "phân tích" chuỗi, do đó, nó đã phải xác thực chuỗi, do đó, có nghĩa là nó báo cáo lỗi để "sử dụng" chuỗi một cách thống nhất (và trong cả hai trường hợp, đây là một ngoại lệ) .
quy tắc xuất hiện thường xuyên với các ngoại lệ là "chỉ sử dụng ngoại lệ trong trường hợp đặc biệt". Nhưng làm thế nào là một chức năng thư viện được cho là biết hoàn cảnh nào là đặc biệt?
Tuyên bố này có, IMHO, hai gốc:
Hiệu suất : Nếu một chức năng được gọi trong một đường dẫn quan trọng và trường hợp "ngoại lệ" không phải là ngoại lệ, tức là một lượng đáng kể các đường chuyền sẽ liên quan đến việc ném một ngoại lệ, sau đó trả tiền mỗi lần cho máy móc ngoại lệ không có ý nghĩa , và có thể quá chậm.
Địa phương xử lý lỗi : Nếu một hàm được gọi và ngoại lệ được ngay lập tức bị bắt và xử lý, sau đó có rất ít điểm trong ném một ngoại lệ, như việc xử lý lỗi sẽ tiết hơn với các catch
so với một if
.
Thí dụ:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Đây là nơi các chức năng như TryParse
so với Parse
hoạt động: Một phiên bản khi mã cục bộ dự kiến chuỗi được phân tích cú pháp là hợp lệ, một phiên bản khi mã cục bộ giả định rằng nó thực sự được mong đợi (nghĩa là không đặc biệt) rằng phân tích cú pháp sẽ thất bại.
Thật vậy, stof
chỉ là (được định nghĩa là) một trình bao bọc xung quanh strtof
, vì vậy nếu bạn không muốn có ngoại lệ, hãy sử dụng gói đó.
Vì vậy, làm thế nào tôi phải quyết định xem tôi có nên sử dụng ngoại lệ hay không cho một chức năng cụ thể?
IMHO, bạn có hai trường hợp:
Chức năng như "Thư viện" (thường được sử dụng lại trong các ngữ cảnh khác nhau): Về cơ bản, bạn không thể quyết định. Có thể cung cấp cả hai phiên bản, có thể một phiên bản báo cáo lỗi và một trình bao bọc chuyển đổi lỗi được trả về thành một ngoại lệ.
Chức năng "Ứng dụng" (cụ thể cho một đốm mã ứng dụng, có thể được sử dụng lại một số, nhưng bị hạn chế bởi kiểu xử lý lỗi ứng dụng, v.v.): Ở đây, nó thường phải được cắt khá rõ ràng. Nếu (các) đường dẫn mã gọi các hàm xử lý các ngoại lệ theo cách hữu ích và hữu ích, hãy sử dụng các ngoại lệ để báo cáo bất kỳ lỗi nào (nhưng xem bên dưới) . Nếu mã ứng dụng dễ đọc và viết hơn cho kiểu trả về lỗi, bằng mọi cách, hãy sử dụng mã đó.
Tất nhiên, sẽ có những nơi ở giữa - chỉ cần sử dụng những gì cần và ghi nhớ YAGNI.
Cuối cùng, tôi nghĩ rằng tôi nên quay lại câu hỏi FAQ,
Không sử dụng throw để chỉ ra lỗi mã hóa khi sử dụng hàm. Sử dụng xác nhận hoặc cơ chế khác để gửi quy trình vào trình gỡ lỗi hoặc để phá vỡ quy trình ...
Tôi đăng ký này cho tất cả các lỗi rõ ràng cho thấy có gì đó bị rối loạn nghiêm trọng hoặc mã cuộc gọi rõ ràng không biết nó đang làm gì.
Nhưng khi điều này là thích hợp thường có tính ứng dụng cao, do đó hãy xem miền thư viện so với miền ứng dụng.
Điều này rơi vào câu hỏi về việc và làm thế nào để xác nhận các điều kiện tiên quyết trong cuộc gọi , nhưng tôi sẽ không đi sâu vào vấn đề đó, câu trả lời đã quá lâu :-)