Xử lý Ngoại lệ (Exception Handling)
📖 Giới thiệu
Xử lý ngoại lệ là cơ chế để xử lý các lỗi runtime một cách có kiểm soát. Thay vì để chương trình crash, ta có thể "bắt" lỗi và xử lý phù hợp.
Ví dụ đơn giản đầu tiên
cpp
#include <iostream>
#include <stdexcept>
using namespace std;
int chia(int a, int b) {
if (b == 0) {
throw runtime_error("Không thể chia cho 0!");
}
return a / b;
}
int main() {
try {
cout << "10 / 2 = " << chia(10, 2) << endl;
cout << "10 / 0 = " << chia(10, 0) << endl; // Sẽ throw exception
cout << "Dòng này sẽ không được thực thi" << endl;
}
catch (const runtime_error& e) {
cout << "Lỗi: " << e.what() << endl;
}
cout << "Chương trình tiếp tục chạy..." << endl;
return 0;
}🔧 Cú pháp
Try-Catch cơ bản
cpp
try {
// Code có thể gây exception
}
catch (exception_type& e) {
// Xử lý exception
}
catch (...) {
// Bắt tất cả exception khác
}Throw Exception
cpp
// Throw built-in exception
throw runtime_error("Error message");
// Throw custom exception
throw MyCustomException("Custom error");
// Re-throw current exception
catch (exception& e) {
// Some handling
throw; // Re-throw
}Custom Exception Class
cpp
class MyException : public exception {
private:
string message;
public:
MyException(const string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};🔬 Phân tích & Giải thích
Các loại Exception trong C++
1. Standard Exceptions
cpp
#include <stdexcept>
// logic_error family
logic_error // Logic errors
invalid_argument // Invalid argument
domain_error // Math domain error
length_error // Length too long
out_of_range // Out of range
// runtime_error family
runtime_error // Runtime errors
range_error // Range error
overflow_error // Arithmetic overflow
underflow_error // Arithmetic underflow2. System Exceptions
cpp
#include <system_error>
system_error // System errors
bad_alloc // Memory allocation failed
bad_cast // Dynamic cast failed
bad_typeid // Typeid of null pointerException Safety Levels
1. No-throw guarantee: Không bao giờ throw exception 2. Strong exception safety: Nếu throw, trạng thái không đổi 3. Basic exception safety: Nếu throw, object vẫn valid 4. No exception safety: Không đảm bảo gì
💻 Ví dụ minh họa
Ví dụ 1: Quản lý Tài khoản Ngân hàng với Exception
cpp
#include <iostream>
#include <string>
#include <stdexcept>
#include <vector>
using namespace std;
// Custom exceptions
class InsufficientFundsException : public runtime_error {
public:
InsufficientFundsException(double amount, double balance)
: runtime_error("Số dư không đủ: cần " + to_string(amount) +
", có " + to_string(balance)) {}
};
class InvalidAmountException : public invalid_argument {
public:
InvalidAmountException(double amount)
: invalid_argument("Số tiền không hợp lệ: " + to_string(amount)) {}
};
class AccountNotFoundException : public logic_error {
public:
AccountNotFoundException(const string& account_id)
: logic_error("Không tìm thấy tài khoản: " + account_id) {}
};
class AccountAlreadyExistsException : public logic_error {
public:
AccountAlreadyExistsException(const string& account_id)
: logic_error("Tài khoản đã tồn tại: " + account_id) {}
};
// Bank Account class
class BankAccount {
private:
string account_id;
string owner_name;
double balance;
vector<string> transaction_history;
void add_transaction(const string& transaction) {
transaction_history.push_back(transaction);
if (transaction_history.size() > 100) { // Giới hạn 100 giao dịch
transaction_history.erase(transaction_history.begin());
}
}
public:
BankAccount(const string& id, const string& name, double initial_balance = 0)
: account_id(id), owner_name(name), balance(initial_balance) {
if (initial_balance < 0) {
throw InvalidAmountException(initial_balance);
}
add_transaction("Tạo tài khoản với số dư: " + to_string(initial_balance));
cout << "✅ Tạo tài khoản: " << account_id << " (" << owner_name << ")" << endl;
}
void deposit(double amount) {
if (amount <= 0) {
throw InvalidAmountException(amount);
}
balance += amount;
add_transaction("Gửi tiền: +" + to_string(amount) + " (Số dư: " + to_string(balance) + ")");
cout << "💰 Gửi " << amount << " VND. Số dư mới: " << balance << " VND" << endl;
}
void withdraw(double amount) {
if (amount <= 0) {
throw InvalidAmountException(amount);
}
if (amount > balance) {
throw InsufficientFundsException(amount, balance);
}
balance -= amount;
add_transaction("Rút tiền: -" + to_string(amount) + " (Số dư: " + to_string(balance) + ")");
cout << "💸 Rút " << amount << " VND. Số dư còn lại: " << balance << " VND" << endl;
}
void transfer(BankAccount& to_account, double amount) {
if (amount <= 0) {
throw InvalidAmountException(amount);
}
if (amount > balance) {
throw InsufficientFundsException(amount, balance);
}
// Transaction atomicity simulation
try {
this->withdraw(amount);
to_account.deposit(amount);
add_transaction("Chuyển đến " + to_account.get_account_id() + ": -" + to_string(amount));
to_account.add_transaction("Nhận từ " + account_id + ": +" + to_string(amount));
cout << "🔄 Chuyển " << amount << " VND từ " << account_id
<< " đến " << to_account.get_account_id() << endl;
}
catch (...) {
// Rollback logic would go here in real implementation
throw; // Re-throw the exception
}
}
double get_balance() const {
return balance;
}
string get_account_id() const {
return account_id;
}
string get_owner_name() const {
return owner_name;
}
void print_statement() const {
cout << "\n=== SỔ GIAO DỊCH ===" << endl;
cout << "Tài khoản: " << account_id << " (" << owner_name << ")" << endl;
cout << "Số dư hiện tại: " << balance << " VND" << endl;
cout << "\nLịch sử giao dịch (5 giao dịch gần nhất):" << endl;
int start = max(0, (int)transaction_history.size() - 5);
for (int i = start; i < transaction_history.size(); i++) {
cout << (i + 1) << ". " << transaction_history[i] << endl;
}
cout << "====================" << endl;
}
};
// Bank Management System
class BankSystem {
private:
vector<BankAccount> accounts;
public:
void create_account(const string& account_id, const string& owner_name, double initial_balance = 0) {
// Check if account already exists
for (const auto& acc : accounts) {
if (acc.get_account_id() == account_id) {
throw AccountAlreadyExistsException(account_id);
}
}
accounts.emplace_back(account_id, owner_name, initial_balance);
}
BankAccount& find_account(const string& account_id) {
for (auto& acc : accounts) {
if (acc.get_account_id() == account_id) {
return acc;
}
}
throw AccountNotFoundException(account_id);
}
void process_transaction(const string& from_id, const string& to_id, double amount) {
try {
BankAccount& from_account = find_account(from_id);
BankAccount& to_account = find_account(to_id);
from_account.transfer(to_account, amount);
cout << "✅ Giao dịch thành công!" << endl;
}
catch (const exception& e) {
cout << "❌ Giao dịch thất bại: " << e.what() << endl;
throw; // Re-throw để caller xử lý
}
}
void print_all_accounts() const {
cout << "\n=== DANH SÁCH TÀI KHOẢN ===" << endl;
for (const auto& acc : accounts) {
cout << acc.get_account_id() << " - " << acc.get_owner_name()
<< " - " << acc.get_balance() << " VND" << endl;
}
cout << "===========================" << endl;
}
};
// Safe banking operations with comprehensive error handling
void safe_banking_demo() {
cout << "=== DEMO HỆ THỐNG NGÂN HÀNG AN TOÀN ===" << endl;
BankSystem bank;
try {
// Tạo tài khoản
cout << "\n--- Tạo tài khoản ---" << endl;
bank.create_account("ACC001", "Nguyễn Văn A", 1000000);
bank.create_account("ACC002", "Trần Thị B", 500000);
bank.create_account("ACC003", "Lê Văn C", 0);
bank.print_all_accounts();
// Test các operations thành công
cout << "\n--- Giao dịch thành công ---" << endl;
BankAccount& acc1 = bank.find_account("ACC001");
BankAccount& acc2 = bank.find_account("ACC002");
acc1.deposit(500000);
acc2.withdraw(100000);
bank.process_transaction("ACC001", "ACC002", 200000);
bank.print_all_accounts();
// Test các lỗi
cout << "\n--- Test xử lý lỗi ---" << endl;
// Test 1: Tài khoản đã tồn tại
try {
bank.create_account("ACC001", "Test User");
}
catch (const AccountAlreadyExistsException& e) {
cout << "🔴 " << e.what() << endl;
}
// Test 2: Số tiền không hợp lệ
try {
acc1.deposit(-100);
}
catch (const InvalidAmountException& e) {
cout << "🔴 " << e.what() << endl;
}
// Test 3: Số dư không đủ
try {
acc2.withdraw(1000000);
}
catch (const InsufficientFundsException& e) {
cout << "🔴 " << e.what() << endl;
}
// Test 4: Tài khoản không tồn tại
try {
bank.find_account("ACC999");
}
catch (const AccountNotFoundException& e) {
cout << "🔴 " << e.what() << endl;
}
// Test 5: Giao dịch phức tạp với multiple exceptions
cout << "\n--- Test giao dịch phức tạp ---" << endl;
try {
bank.process_transaction("ACC002", "ACC003", 1000000); // Không đủ tiền
}
catch (const exception& e) {
cout << "🔴 Giao dịch bị từ chối: " << e.what() << endl;
}
// In statement cuối cùng
cout << "\n--- Sổ giao dịch cuối ---" << endl;
acc1.print_statement();
acc2.print_statement();
}
catch (const exception& e) {
cout << "💥 Lỗi hệ thống: " << e.what() << endl;
}
catch (...) {
cout << "💥 Lỗi không xác định!" << endl;
}
}
int main() {
safe_banking_demo();
return 0;
}Ví dụ 2: File Processing với Exception Handling
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <vector>
using namespace std;
// Custom file exceptions
class FileOpenException : public runtime_error {
public:
FileOpenException(const string& filename)
: runtime_error("Không thể mở file: " + filename) {}
};
class FileReadException : public runtime_error {
public:
FileReadException(const string& filename)
: runtime_error("Lỗi đọc file: " + filename) {}
};
class FileWriteException : public runtime_error {
public:
FileWriteException(const string& filename)
: runtime_error("Lỗi ghi file: " + filename) {}
};
class InvalidDataException : public logic_error {
public:
InvalidDataException(const string& data, int line_number)
: logic_error("Dữ liệu không hợp lệ tại dòng " + to_string(line_number) + ": " + data) {}
};
// RAII File handler
class SafeFileHandler {
private:
fstream file;
string filename;
ios::openmode mode;
public:
SafeFileHandler(const string& fname, ios::openmode m)
: filename(fname), mode(m) {
file.open(filename, mode);
if (!file.is_open()) {
throw FileOpenException(filename);
}
cout << "📂 Mở file: " << filename << endl;
}
~SafeFileHandler() {
if (file.is_open()) {
file.close();
cout << "🔒 Đóng file: " << filename << endl;
}
}
// Disable copy to ensure single ownership
SafeFileHandler(const SafeFileHandler&) = delete;
SafeFileHandler& operator=(const SafeFileHandler&) = delete;
// Move constructor/assignment
SafeFileHandler(SafeFileHandler&& other) noexcept
: file(move(other.file)), filename(move(other.filename)), mode(other.mode) {}
string read_line() {
if (!file.is_open()) {
throw FileReadException(filename);
}
string line;
if (!getline(file, line)) {
if (file.bad()) {
throw FileReadException(filename);
}
// EOF is not an error, return empty string
}
return line;
}
vector<string> read_all_lines() {
vector<string> lines;
string line;
while (!file.eof()) {
line = read_line();
if (!line.empty() || !file.eof()) {
lines.push_back(line);
}
}
cout << "📖 Đọc " << lines.size() << " dòng từ " << filename << endl;
return lines;
}
void write_line(const string& line) {
if (!file.is_open()) {
throw FileWriteException(filename);
}
file << line << endl;
if (file.fail()) {
throw FileWriteException(filename);
}
}
void write_lines(const vector<string>& lines) {
for (const auto& line : lines) {
write_line(line);
}
cout << "✍️ Ghi " << lines.size() << " dòng vào " << filename << endl;
}
bool is_open() const {
return file.is_open();
}
bool eof() const {
return file.eof();
}
};
// Data processor với exception handling
class DataProcessor {
public:
struct StudentRecord {
string name;
int age;
double score;
StudentRecord(const string& n, int a, double s) : name(n), age(a), score(s) {}
string to_string() const {
return name + "," + std::to_string(age) + "," + std::to_string(score);
}
};
static vector<StudentRecord> parse_csv_file(const string& filename) {
vector<StudentRecord> records;
try {
SafeFileHandler file(filename, ios::in);
vector<string> lines = file.read_all_lines();
for (int i = 0; i < lines.size(); i++) {
if (lines[i].empty()) continue; // Skip empty lines
try {
StudentRecord record = parse_csv_line(lines[i], i + 1);
records.push_back(record);
}
catch (const InvalidDataException& e) {
cout << "⚠️ Bỏ qua dòng lỗi: " << e.what() << endl;
// Continue processing other lines
}
}
cout << "✅ Đọc thành công " << records.size() << " bản ghi" << endl;
}
catch (const FileOpenException& e) {
cout << "❌ " << e.what() << endl;
throw; // Re-throw để caller xử lý
}
catch (const FileReadException& e) {
cout << "❌ " << e.what() << endl;
throw;
}
return records;
}
static void save_csv_file(const string& filename, const vector<StudentRecord>& records) {
try {
SafeFileHandler file(filename, ios::out);
// Write header
file.write_line("Name,Age,Score");
// Write data
vector<string> lines;
for (const auto& record : records) {
lines.push_back(record.to_string());
}
file.write_lines(lines);
cout << "✅ Lưu thành công " << records.size() << " bản ghi" << endl;
}
catch (const FileOpenException& e) {
cout << "❌ " << e.what() << endl;
throw;
}
catch (const FileWriteException& e) {
cout << "❌ " << e.what() << endl;
throw;
}
}
static StudentRecord parse_csv_line(const string& line, int line_number) {
vector<string> fields;
string field = "";
// Simple CSV parsing
for (char c : line) {
if (c == ',') {
fields.push_back(field);
field = "";
} else {
field += c;
}
}
fields.push_back(field); // Last field
if (fields.size() != 3) {
throw InvalidDataException(line, line_number);
}
try {
string name = fields[0];
int age = stoi(fields[1]);
double score = stod(fields[2]);
// Validate data
if (name.empty()) {
throw InvalidDataException("Tên rỗng", line_number);
}
if (age < 0 || age > 120) {
throw InvalidDataException("Tuổi không hợp lệ: " + to_string(age), line_number);
}
if (score < 0 || score > 10) {
throw InvalidDataException("Điểm không hợp lệ: " + to_string(score), line_number);
}
return StudentRecord(name, age, score);
}
catch (const invalid_argument& e) {
throw InvalidDataException("Định dạng số không hợp lệ", line_number);
}
catch (const out_of_range& e) {
throw InvalidDataException("Số quá lớn", line_number);
}
}
static void print_statistics(const vector<StudentRecord>& records) {
if (records.empty()) {
cout << "Không có dữ liệu để thống kê" << endl;
return;
}
double total_score = 0;
int total_age = 0;
double max_score = records[0].score;
double min_score = records[0].score;
for (const auto& record : records) {
total_score += record.score;
total_age += record.age;
max_score = max(max_score, record.score);
min_score = min(min_score, record.score);
}
cout << "\n=== THỐNG KÊ ===" << endl;
cout << "Số học sinh: " << records.size() << endl;
cout << "Điểm trung bình: " << (total_score / records.size()) << endl;
cout << "Tuổi trung bình: " << (total_age / records.size()) << endl;
cout << "Điểm cao nhất: " << max_score << endl;
cout << "Điểm thấp nhất: " << min_score << endl;
cout << "===============" << endl;
}
};
void file_processing_demo() {
cout << "=== DEMO XỬ LÝ FILE VỚI EXCEPTION ===" << endl;
// Tạo dữ liệu mẫu
vector<DataProcessor::StudentRecord> sample_data = {
{"Nguyễn Văn A", 20, 8.5},
{"Trần Thị B", 19, 7.2},
{"Lê Văn C", 21, 9.1},
{"Phạm Thị D", 20, 6.8}
};
try {
// Test 1: Ghi file thành công
cout << "\n--- Test ghi file ---" << endl;
DataProcessor::save_csv_file("students.csv", sample_data);
// Test 2: Đọc file thành công
cout << "\n--- Test đọc file ---" << endl;
vector<DataProcessor::StudentRecord> loaded_data =
DataProcessor::parse_csv_file("students.csv");
DataProcessor::print_statistics(loaded_data);
// Test 3: Tạo file với dữ liệu lỗi
cout << "\n--- Test file với dữ liệu lỗi ---" << endl;
try {
SafeFileHandler error_file("error_data.csv", ios::out);
error_file.write_line("Name,Age,Score");
error_file.write_line("Nguyễn Văn E,22,8.0"); // OK
error_file.write_line("Trần Thị F,-5,7.0"); // Tuổi âm
error_file.write_line("Lê Văn G,25,15.0"); // Điểm > 10
error_file.write_line(",30,8.0"); // Tên rỗng
error_file.write_line("Phạm Thị H,abc,8.0"); // Tuổi không phải số
error_file.write_line("Hoàng Văn I,25"); // Thiếu trường
}
catch (const exception& e) {
cout << "Lỗi tạo file test: " << e.what() << endl;
}
// Đọc file có lỗi
vector<DataProcessor::StudentRecord> partial_data =
DataProcessor::parse_csv_file("error_data.csv");
cout << "Dữ liệu hợp lệ được đọc:" << endl;
for (const auto& record : partial_data) {
cout << "- " << record.name << ", " << record.age << ", " << record.score << endl;
}
// Test 4: Đọc file không tồn tại
cout << "\n--- Test file không tồn tại ---" << endl;
try {
DataProcessor::parse_csv_file("nonexistent.csv");
}
catch (const FileOpenException& e) {
cout << "🔴 " << e.what() << endl;
}
}
catch (const exception& e) {
cout << "💥 Lỗi chung: " << e.what() << endl;
}
catch (...) {
cout << "💥 Lỗi không xác định trong quá trình xử lý file!" << endl;
}
}
int main() {
file_processing_demo();
return 0;
}🏋️ Thực hành
Bài tập 1: Calculator với Exception
Tạo class Calculator với exception handling cho:
- Chia cho 0
- Căn bậc hai của số âm
- Logarit của số âm hoặc 0
Bài tập 2: Custom Array với Exception
Tạo class SafeArray với:
IndexOutOfBoundsExceptionArrayFullExceptionEmptyArrayException
Bài tập 3: Network Connection Simulator
Tạo class mô phỏng kết nối mạng với:
ConnectionTimeoutExceptionNetworkUnavailableExceptionAuthenticationFailedException
📋 Tóm tắt
Exception Handling: Xử lý lỗi runtime có kiểm soát
Từ khóa:
try: Khối code có thể gây exceptioncatch: Bắt và xử lý exceptionthrow: Ném exceptionfinally: Không có trong C++ (dùng RAII)
Best Practices:
- Chỉ throw cho exceptional cases
- Sử dụng RAII cho resource management
- Catch by reference
- Đừng throw trong destructor
Lợi ích:
- Code robust hơn
- Tách biệt error handling khỏi business logic
- Có thể recovery từ lỗi
Bài tiếp theo: Vector - Bắt đầu tìm hiểu về Standard Template Library.