Xử lý ngoại lệ trong OOP
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 81 trên 104.
Xử lý ngoại lệ (exception handling) trong C++ cung cấp một cách có cấu trúc để phát hiện và xử lý lỗi khi chạy chương trình. Trong lập trình hướng đối tượng, ngoại lệ tích hợp tự nhiên với hệ thống phân cấp lớp, cho phép bạn tạo các kiểu lỗi có ý nghĩa và chứa thông tin về sự cố đã xảy ra.
Cơ chế cơ bản sử dụng ba từ khóa: try, throw và catch. Đoạn code có thể gặp lỗi được đặt trong khối try, lỗi được báo bằng throw, còn khối catch xử lý các kiểu ngoại lệ cụ thể:
#include <iostream>
#include <stdexcept>
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount account(100.0);
try {
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Balance: " << account.getBalance() << "\n";
}Thư viện chuẩn C++ cung cấp một hệ thống phân cấp các lớp ngoại lệ bắt nguồn từ std::exception. Các lớp dẫn xuất phổ biến gồm std::runtime_error cho các vấn đề khi chạy và std::logic_error cho các lỗi lập trình. Tất cả ngoại lệ chuẩn đều có phương thức what() trả về thông báo lỗi.
Khi một ngoại lệ được ném ra, ngăn xếp sẽ tháo lui: các hàm hủy được gọi cho mọi đối tượng cục bộ khi chương trình tìm khối catch phù hợp. Nhờ vậy, chương trình vẫn dọn dẹp đúng cách ngay cả khi xảy ra lỗi, đó là lý do RAII và xử lý ngoại lệ kết hợp với nhau rất hiệu quả.
Thử thách
DễHãy tạo một máy tính chia an toàn để minh họa cách xử lý ngoại lệ trong ngữ cảnh lập trình hướng đối tượng. Bạn sẽ tạo lớp Calculator để thực hiện phép chia và ném các ngoại lệ có ý nghĩa khi xảy ra sự cố, sau đó xử lý các ngoại lệ đó một cách phù hợp trong chương trình chính.
Bạn sẽ sắp xếp code trong ba tệp:
Calculator.h: Khai báo lớpCalculatorđể thực hiện các phép toán và xử lý lỗi phù hợp.Lớp
Calculatorcần có phương thứcdivide(double numerator, double denominator)trả về kết quả phép chia. Tuy nhiên, nếu mẫu số bằng không, phương thức cần ném ngoại lệstd::runtime_errorvới thông báoDivision by zero. Nếu tử số hoặc mẫu số là số âm, phương thức cần ném ngoại lệstd::invalid_argumentvới thông báoNegative values not allowed.Thêm cả phương thức
safeSqrt(double value)để trả về căn bậc hai của giá trị. Nếu giá trị là số âm, hãy ném ngoại lệstd::runtime_errorvới thông báoCannot compute square root of negative number. Bạn cần thêm<cmath>để dùng hàmsqrt.Calculator.cpp: Triển khai các phương thức đã khai báo trong tệp tiêu đề. Nhớ thêm các thư viện cần thiết (<stdexcept>và<cmath>) và kiểm tra điều kiện trước khi thực hiện phép toán.main.cpp: Đọc ba đầu vào, mỗi đầu vào nằm trên một dòng riêng:- Loại phép toán:
dividehoặcsqrt - Số thứ nhất (double)
- Số thứ hai (double): chỉ dùng cho phép chia, bỏ qua khi tính căn bậc hai
Tạo một đối tượng
Calculatorvà thực hiện phép toán được yêu cầu bên trong khối try. Nếu phép toán thành công, in raResult: [value]. Nếu bắt được ngoại lệ, in raError: [message], trong đó thông báo được lấy từ phương thứcwhat()của ngoại lệ.Sau khối try-catch, bất kể có xảy ra ngoại lệ hay không, hãy in ra
Calculation completeđể minh họa rằng chương trình tiếp tục chạy sau khi xử lý ngoại lệ.- Loại phép toán:
Ví dụ, với đầu vào divide, 10 và 2:
Result: 5
Calculation completeVới đầu vào divide, 10 và 0:
Error: Division by zero
Calculation completeVới đầu vào divide, -5 và 2:
Error: Negative values not allowed
Calculation completeVới đầu vào sqrt, 16 và 0:
Result: 4
Calculation completeVới đầu vào sqrt, -9 và 0:
Error: Cannot compute square root of negative number
Calculation completeHãy chú ý cách chương trình xử lý lỗi một cách phù hợp bằng cách bắt ngoại lệ và hiển thị thông báo rõ nghĩa, rồi tiếp tục chạy. Cách làm này giúp phần xử lý lỗi gọn gàng và phần logic chính tập trung vào trường hợp thành công.
Tự làm thử
#include <iostream>
#include <string>
#include "Calculator.h"
using namespace std;
int main() {
// Đọc các giá trị đầu vào
string operation;
double num1, num2;
cin >> operation;
cin >> num1;
cin >> num2;
// Tạo đối tượng Calculator
Calculator calc;
// TODO: Dùng khối try-catch để thực hiện phép toán
// Nếu operation là "divide", gọi calc.divide(num1, num2)
// Nếu operation là "sqrt", gọi calc.safeSqrt(num1)
// Nếu thành công, in ra "Result: " rồi đến kết quả
// Bắt ngoại lệ và in ra "Error: " rồi đến thông báo ngoại lệ (dùng what())
// Sau khối try-catch, luôn in ra "Calculation complete"
return 0;
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học11Các khái niệm OOP nâng cao
Kết hợp so với kế thừaMixin qua CRTPMẫu PimplXóa kiểuLớp enum và định kiểu mạnhXử lý ngoại lệ trong OOPPhân cấp ngoại lệ tùy chỉnh3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online