Menu
CoddyTech

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ả.

challenge icon

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ớp Calculator để thực hiện các phép toán và xử lý lỗi phù hợp.

    Lớp Calculator cần có phương thức divide(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_error với thông báo Division 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_argument với thông báo Negative 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_error với thông báo Cannot compute square root of negative number. Bạn cần thêm <cmath> để dùng hàm sqrt.

  • 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:
    1. Loại phép toán: divide hoặc sqrt
    2. Số thứ nhất (double)
    3. 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 Calculator và 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 ra Result: [value]. Nếu bắt được ngoại lệ, in ra Error: [message], trong đó thông báo được lấy từ phương thức what() 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ệ.

Ví dụ, với đầu vào divide, 10 và 2:

Result: 5
Calculation complete

Với đầu vào divide, 10 và 0:

Error: Division by zero
Calculation complete

Với đầu vào divide, -5 và 2:

Error: Negative values not allowed
Calculation complete

Với đầu vào sqrt, 16 và 0:

Result: 4
Calculation complete

Với đầu vào sqrt, -9 và 0:

Error: Cannot compute square root of negative number
Calculation complete

Hã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;
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C++ online