Xử lý ngoại lệ trong ngôn ngữ C++

Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.

Chắc hẳn mọi người trong chúng ta đều biết, con người không hoàn hảo, ngôn ngữ lập trình do con người tạo nên chắc chắc cũng không hoàn hảo, chương trình do con người viết cũng sẽ không hoàn hảo. Như vậy, sẽ có những trường hợp mã nguồn chúng ta viết ra không giải quyết bao quát hết tất cả trường hợp. Đó là lý do ngôn ngữ C++ hay nhiều ngôn ngữ lập trình khác đưa ra khái niệm: Ngoại lệ (Exception).

Ngoại lệ (exception) là gì?

Ngoại lệ là một vấn đề phát sinh trong khi chương trình thực thi. Ví dụ: chương trình bắt gặp phép toán thực hiện chia cho số 0.

Tại sao cần xử lý ngoại lệ?

Khi viết mã nguồn có khả năng tái sử dụng, việc xử lý ngoại lệ là cần thiết.

Một trong những cách đơn giản nhất để xử lý các lỗi tiềm ẩn là thông qua giá trị trả về (return codes). Ví dụ:

int findFirstChar(const char* string, char ch)
{
    // Step through each character in string
    for (int index=0; index < strlen(string); ++index)
        // If the character matches ch, return its index
        if (string[index] == ch)
            return index;
 
    // If no match was found, return -1
    return -1;
}

Hàm findFirstChar trả về chỉ số đầu tiên khi ký tự ch được tìm thấy trong chuỗi kí tự string. Nếu không tìm thấy kí tự tương ứng, hàm trả về giá trị -1.

Tinh thần của phương pháp sử dụng return codes này khá đơn giản. Tuy nhiên, nó cũng có một vài hạn chế trong một số trường hợp không bình thường:

Đầu tiên, sử dụng giá trị trả về có thể gây khó hiểu. Liệu rằng -1 có ý nghĩa mặc định là một lỗi được tìm thấy? Làm như thế nào để trả về -1 khi giá trị trả về của hàm có kiểu unsigned int?

Thứ hai, hàm chỉ có thể trả về một giá trị duy nhất, thật khó để thông báo cho người dùng 2 lỗi xảy ra đồng thời khi chỉ có một giá trị trả về.

Tiếp theo, khi một hàm trả về giá trị lỗi, tại nơi gọi hàm đó chưa chắc đã có phần code để xử lý lỗi trả về đó.

Cơ chế xử lý ngoại lệ trong ngôn ngữ C++ cung cấp cho chúng ta một giải pháp tốt hơn.

Xử lý ngoại lệ

Ngôn ngữ C++ định nghĩa sẵn cho chúng ta 3 từ khóa để sử dụng kết hợp với nhau trong quá trình xử lý ngoại lệ: throw, try, và catch.

Ném ra ngoại lệ (throw exception)

Trong đời sống hằng ngày, chúng ta có thể phát đi rất nhiều tín hiệu khác nhau.

Ví dụ, trong một trận bóng đá, khi trọng tài phát hiện một trường hợp cầu thủ gian lận hoặc phạm lỗi, trọng tài có thể thổi còi, phất cờ, … để thông báo hành vi sai trái trong quá trình thi đấu.

Tại thời điểm này, trận đấu phải tạm hoãn, cho đến khi một quả đá phạt được thực hiện xong, trận đấu tiếp tục diễn ra như bình thường.

Trong ngôn ngữ C++, một ngoại lệ được ném ra sẽ thông báo rằng có điều gì đó bất ổn trong chương trình.

Để ném ra một ngoại lệ, chúng ta chỉ cần đơn giản sử dụng từ khóa throw.

throw -1; // throw a literal integer value
throw ENUM_INVALID_INDEX; // throw an enum value
throw "Can not take square root of negative number"; // throw a literal C-style (const char*) string
throw dX; // throw a double variable that was previously defined
throw MyException("Fatal Error"); // Throw an object of class MyException

Tìm kiếm ngoại lệ

Ném ra ngoại lệ chỉ mới là một phần trong quá trình xử lý ngoại lệ. Quay trở lại với minh họa trận bóng đá, điều gì xảy ra khi một trường hợp phạm lỗi bị phát hiện? Trận bóng phải tạm dừng.

Trong ngôn ngữ C++, chúng ta sử dụng từ khóa try như một người trọng tài giám sát một khối lệnh, tìm kiếm những trường hợp ngoại lệ được ném ra từ bên trong khối lệnh đó.

try
{
    // Statements that may throw exceptions you want to handle go here
    throw -1; // here's a trivial throw statement
}

Lưu ý rằng khối lệnh được từ khóa try canh giữ không định nghĩa cách chương trình xử lý ngoại lệ như thế nào. Nó chỉ đơn giản nói cho chương trình biết rằng, “Nếu có bất kỳ ngoại lệ nào được ném ra, hãy tóm lấy nó!”

Xử lý ngoại lệ

Cuối cùng, khi một trường hợp phạm lỗi bị phát hiện, trận đấu sẽ tạm dừng cho đến khi trường hợp phạm lỗi được xử lý, có thể là bằng một quả sút phạt.

Trong ngôn ngữ C++, chương trình sẽ bị tạm dừng nếu một ngoại lệ bị bắt lấy, cho đến khi thực thi xong phần xử lý ngoại lệ. Chúng ta định nghĩa phần xử lý ngoại lệ trong khối lệnh có từ khóa catch.

catch (int x)
{
    // Handle an exception of type int here
    std::cerr << "We caught an int exception with value" << x << '\n';
}

Tại đây, khối lệnh catch (int x) sẽ xử lý những ngoại lệ được ném ra có kiểu dữ liệu integer.

Khi một ngoại lệ bị bắt trong khối lệnh của từ khóa try, chương trình sẽ tìm đến khối lệnh catch để xử lý nó.

Đối với phần xử lý ngoại lệ không dùng đến phần tham số bên trong khối lệnh, chúng ta có thể bỏ qua việc đặt tên biến cho tham số ngoại lệ.

catch (double) // note: no variable name since we don't use it in the catch block below
{
    // Handle exception of type double here
    std::cerr << "We caught an exception of type double" << '\n';
}

Cách này giúp chương trình tránh được những cảnh báo liên quan đến việc biến đã khai báo nhưng không được sử dụng.

Kết hợp throw, try, và catch với nhau

Dưới đây là một ví dụ đơn giản cho thấy bộ ba throw, try, catch hoạt động với nhau như thế nào:

#include <iostream>
 
int main()
{
    try
    {
        throw 4.5; // throw exception of type double
        std::cout << "This never prints\n";
    }
    catch(double x) // handle exception of type double
    {
        std::cerr << "We caught a double of value: " << x << '\n';
    }
 
    return 0;
}

Khi chạy chương trình, chúng ta sẽ thấy dòng lệnh đứng sau phần ném ngoại lệ sẽ không được thực thi. Vì khi ngoại lệ được ném ra, ngay lập tức chương trình tạm dừng và chuyển đến phần xử lý ngoại lệ. Sau khi thực thi xong phần xử lý ngoại lệ, chương trình tiếp tục hoạt động bình thường kể từ nơi chương trình vừa xử lý ngoại lệ xong.

Một ví dụ khác thực tế hơn

Chúng ta cùng đến với một ví dụ khác điển hình hơn:

#include "math.h" // for sqrt() function
#include <iostream>
 
int main()
{
    std::cout << "Enter a number: ";
    double x;
    std::cin >> x;
 
    try // Look for exceptions that occur within try block and route to attached catch block(s)
    {
        // If the user entered a negative number, this is an error condition
        if (x < 0.0)
            throw "Can not take sqrt of negative number"; // throw exception of type const char*
 
        // Otherwise, print the answer
        std::cout << "The sqrt of " << x << " is " << sqrt(x) << '\n';
    }
    catch (const char* exception) // catch exceptions of type const char*
    {
        std::cerr << "Error: " << exception << '\n';
    }
}

Trong chương trình này, người dùng được yêu cầu nhập vào một con số. Nếu họ nhập vào giá trị dương, không có ngoại lệ nào được ném ra, và giá trị được in ra bình thường.

Enter a number: 9
The sqrt of 9 is 3

Trong trường hợp người dùng nhập vào giá trị âm, chương trình sẽ ném ra một ngoại lệ kiểu char*.

Enter a number: -4
Error: Can not take sqrt of negative number

=========================================================

Chúng ta vừa đi qua phần xử lý ngoại lệ cơ bản trong ngôn ngữ C++. Đa số các ngôn ngữ lập trình bậc cao đều có cơ chế xử lý ngoại lệ tương tự nhau.

Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.

Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn.

www.daynhauhoc.com

8 Likes
catch (const char* exception) 

vì sao mình phải dùng const , và vì sao phải dùng con trỏ vậy ạ ?

  1. Vì chuỗi như vậy được viết thẳng vào exe.
  2. Dùng chuỗi đó để không sinh ngoại lệ chồng ngoại lệ rất khó giải quyết.
2 Likes

Cảm ơn anh
1.thế nào là viết thẳng vào exe ạ?
2.những ngoại lệ có thể xảy ra là gì ạ ?

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?