Từ 1 tới 2 trên tổng số 2 kết quả

Đề tài: Exception Handling trong lập trình C++

  1. #1
    Ngày gia nhập
    07 2006
    Nơi ở
    Hanoi, Vietnam
    Bài viết
    2,750

    Mặc định Exception Handling trong lập trình C++

    Exception Handling trong C++

    Trong Java chắc chắn bạn sẽ không lạ lẫm gì với exception handling , một cơ chế giúp cho bạn dễ dàng xử lý các trường hợp, tình huống lỗi mà chương trình gặp phải trong quá trình thực thi. Thế còn trong C++ thì sao ? Đối với ANSI C++ thì tính năng này hòan tòan đã được tích hợp và cho phép bạn một cơ chế linh họat và dễ dàng cho việc xứ lý các tình huống lỗi. Bài viết này sẽ nhằm giới thiệu với các bạn exception handling trong ANSI C++ và bàn đến những lợi ích mà exception handling mang lại so với cách xử lý lỗi truyền thống.

    1. Giới thiệu về Exception
    Exception là gì ? Nghĩa chính xác của từ exception là những trường hợp ngọai lệ, chúng ta không gọi nó là các lỗi thực thi vì nó thực sự chưa xảy ra, chúng ta chỉ đơn giản kiểm tra và bắt các trường hợp ngọai lệ mà chúng ta biết chắc chắn sẽ gây ra lỗi … Chúng ta sẽ thấy trong một ví dụ cụ thể sau. Trong một chương trình mộ phỏng các phản ứng hạt nhân, chúng ta cần đưa vào các tham số phương trình hoặc lưu lượng các chất cho thích hợp, dựa trên đó mà chương trình sẽ mô phỏng và tính tóan chính xác tầm ảnh hưởng của phản ứng, thế nhưng trong một số trường hợp chúng ta đưa vào các tham số hoặc lưu lượng quá mức làm cho phản ứng vượt quá mức không chính xác thì khi đó chương trình cần thông báo cho chúng ta lỗi ví dụ như :”Tác hại nghiêm trọng – Không thực hiện được” … Khi đó buộc chúng ta phải hủy bỏ phản ứng và quay trở lại vị trí ban đầu là nhập vào các tham số và lưu lượng chính xác. Chương trình phải làm việc kiểm tra và bắt hết các trường hợp ’ngọai lệ’ và nguy hiểm trước khi cho nguy hiểm đó có thể xảy ra. Nếu như chương trình cho phép phản ứng diễn ra rồi mới kiểm tra tầm tác hại thì coi như nó cũng tiêu tùng luôn rồi còn gì !!!!

    2. Exception Handling
    Hãy xét một đọan code nhỏ sau:
    C++ Code:
    1. if (kiem_tra_tham_so() && kiem_tra_luu_luong()) {
    2. …. tien hanh phan ung
    3. } else {
    4. … in thong bao loi va quay lai
    5. }

    Đọan code trên là cách bắt lỗi thông thường nhưng với cách bắt lỗi trên thì rất kém hiệu quả vì sao ? Hãy xét ví dụ như việc kiểm tra quá trình phản ứng cần thông qua 10 quá trình kiểm tra khác nhau, khi bất kì trong một quá trình nào không đúng thì buộc phải hủy bỏ và quay lại vị trí ban đầu,

    C++ Code:
    1. SYSTEM_WORK = OK;
    2. while (1) {
    3.    … nhap cac tham so;
    4. if (kiem_tra_A()) SYSTEM_WORK = OK; else SYSTEM_WORK = FAIL;
    5.     if (SYSTEM_WORK == OK && kiem_tra_B()) …
    6.     if ….
    7.  
    8.     if (SYTEM_WORK == OK) {
    9. tien_hanh_phan_ung;
    10. exit ;
    11. }
    12. }

    Bạn sẽ thấy là đoạn code trên rất là cồng kềnh và không đẹp … thế nên ANSI C++ đã đưa vào một cơ chế trong phần ngôn ngữ cơ chế để chúng ta kiểm tra và bắt các trường hợp ngọai lệ một cách hiệu quả hơn, cơ chế sử dụng chủ yếu 3 từ khóa là try , catch và throw …Xét đọan code trên bằng cách dùng exception handling

    C++ Code:
    1. try {
    2.     if (!kiem_tra_A()) throw “A fail”;
    3.     if (!kiem_tra_B()) throw “B fail”;
    4.     ….
    5. } catch (string exception) {
    6.     cout << “System fail ::: “ << exception << endl;
    7. }

    Bạn sẽ thấy là đọan code trên nhìn nó gọn và logic hơn rất nhiều và tránh cho chúng ta rất nhiều trường hợp rắc rối không cần thiết với cách thức truyền thống. Trong phần đọan mã sau từ khóa try thì chương trình sẽ kiểm tra nếu như có bất kì một exception nào xảy ra hoặc là ‘throw exception’ được rồi thì chương trình sẽ lập tức chấm dứt và chuyển đến phần đoạn mã của từ khóa catch, từ khóa catch sẽ nhận tham số là các exception cần bắt và in ra thông báo lỗi …

    3. Exception Handling trong C++
    Đối với lệnh throw thì tham số là bất kì kiểu dữ liệu nào, từ int, string, class, struct, enum .v.v… trong C++ bạn đều có thể dùng được với ‘throw’ và dựa trên kiểu dữ liệu đó mà lệnh catch sẽ bắt chính xác cho từng exception. Ví dụ nếu chúng ta kiểm tra một phép chia cho một số khác 0 và nhỏ hơn 100 như sau:

    C++ Code:
    1. try {
    2.     if (n == 0) throw “Chia cho 0”; // Chúng ta đ㠑ném’ kiểu string
    3.     if (n > 100) throw n; // Chúng ta đ㠑ném’ kiểu int
    4. } catch (int n) { // Bắt exception kiếu int
    5.     cout << “n thi khong duoc phep < 100” << endl;
    6. } catch (string s) {
    7.     cout << “LOI : “ << s << endl;
    8. }

    Bi giờ chúng ta hãy thử đối với một class

    C++ Code:
    1. class Exception {
    2.  public:
    3.     string CODE;
    4.     Exception(string _code) : CODE(_code) {}
    5. };
    6.  
    7. try {
    8.     if (!kiem_tra_A()) throw Exception(“A that bai”);
    9.     if (!kiem_tra_B()) throw Exception(“B that bai”);
    10.     …
    11. } catch (Exception e)
    12. {
    13.     cout << e.CODE << endl;
    14. }

    Rất ư là tiện lợi phải không ? Có thể nói exception handling là một cơ chế rất mạnh trong các ngôn ngữ lập trình hiện đại, viec thuần thục ứng dụng tính năng này sẽ giúp chương trình của bạn rõ ràng hơn, ít cồng kềnh hơn và tiết kiệm được chi phí, nâng cao năng suất lao động. Điều ràng buộc duy nhất là C++ compiler của bạn phải tương thích với ANSI C++ mới có thể hỗ trợ được tính năng này.

    Bài viết không tránh khỏi các lỗi hoặc nhầm lẫn bằng cách này hay cách khác, rất mong sự góp ý của các bạn để bài viết được hòan thiện hơn, xin cám ơn.

    Bài viết sưu tầm...
    Email: admin[@]congdongcviet.com | CC to: info[@]congdongcviet.com
    Phone: 0972 89 7667 (Office: 04 6329 2380)
    Yahoo & Skype: dreaminess_world (Vui lòng chỉ rõ mục đích ngay khi liên hệ, cảm ơn!)

    Một người nào đó coi thường ý thức kỷ luật cũng có nghĩa là người đó đã coi thường tương lai số phận của chính bản thân người đó. Những người coi thường ý thức kỷ luật sẽ không bao giờ có được sự thành công trong sự nghiệp!

  2. #2
    Ngày gia nhập
    12 2008
    Nơi ở
    Hà Nội
    Bài viết
    374

    Từ khóa throw

    Ta có thể sử dụng từ khóa throw để ném bất kì giá trì nào để miêu tả 1 exception.

    Từ khóa throw không nhất thiết phải nằm trong khối try. Ta có thể ném exception và để cho các caller xử lý exception.

    Khi sử dụng throw để ném 1 exception, luồng thực thi tạm dừng và tìm khối try, nếu không có khối try hoặc khối try không có khối catch xử lý exception phù hợp, thì caller sẽ chịu trách nhiệm xử lý exception, nếu caller này không xử lý được, thì caller trên nữa sẽ xử lý, nếu exception không được xử lý, chương trình sẽ bị ngừng. Quá trình này được gọi là unwinding the stack.

    Ví dụ .

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

    Ví dụ . Minh họa quá trình unwinding the stack.

    C++ Code:
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. void Last() // called by Third()
    5. {
    6.     cout << "Start Last" << endl; // in cái này
    7.     cout << "Last throwing int exception" << endl;
    8.     throw -1; // <-- luồng thực thi chương trình dừng ở đây
    9.     cout << "End Last" << endl; // không thực thi đến cái này
    10.  
    11. }
    12.  
    13. void Third() // called by Second()
    14. {
    15.     cout << "Start Third" << endl; // in cái này
    16.     Last(); // chả thấy try nào ở đây, ném lên tiếp
    17.     cout << "End Third" << endl; // không thực thi đến cái này
    18. }
    19.  
    20. void Second() // called by First()
    21. {
    22.     cout << "Start Second" << endl; // in cái này
    23.     try
    24.     {
    25.         Third(); // ném lên đây
    26.     }
    27.     catch(double) // có 1 thằng catch xử lý kiểu double, nhưng đnag cần kiểu int, vậy ném lên tiếp
    28.     {
    29.          cerr << "Second caught double exception" << endl;
    30.     }
    31.     cout << "End Second" << endl; // không thực thi đến cái này
    32. }
    33.  
    34. void First() // called by main()
    35. {
    36.     cout << "Start First" << endl; // in cái này
    37.     try
    38.     {
    39.         Second(); // ném lên tới đây
    40.     }
    41.     catch (int) // có khối catch xử lý exception kiểu int
    42.     {
    43.          cerr << "First caught int exception" << endl; // vậy xử lý ở đây
    44.     }
    45.     catch (double)
    46.     {
    47.          cerr << "First caught double exception" << endl;
    48.     }
    49.     cout << "End First" << endl; // <-- luồng thực thi chương trình tiếp tục ở đây, nên in tiếp cái này
    50. }
    51.  
    52. int main()
    53. {
    54.     cout << "Start main" << endl; // in cái này
    55.     try
    56.     {
    57.         First();
    58.     }
    59.     catch (int)
    60.     {
    61.          cerr << "main caught int exception" << endl; // không thực thi đến cái này
    62.     }
    63.     cout << "End main" << endl; // in cái này
    64.  
    65.     return 0;
    66. }

    Output :

    Start main
    Start First
    Start Second
    Start Third
    Start Last
    Last throwing int exception
    First caught int exception
    End First
    End main

    Giải thích :

    Để ý rằng, exception được ném từ hàm Last(), luồng thực thi chương trình tạm dừng, hàm Last() không có khối try, nên việc xử lý exception được đẩy lên hàm gọi hàm Last(), là hàm Third(), hàm Third() cũng không có khối try, nên việc xử lý exception được đẩy lên hàm gọi hàm Third(), là hàm Second(), hàm Second() có khối try và khối catch xử lý exception kiểu double, nhưng để ý lại rằng, hàm Last() ném exception kiểu int ( throw -1; ), nên việc xử lý exception này tiếp tục được đẩy lên hàm gọi hàm Second(), là hàm First(), tại đây, có khối try và khối catch xử lý exception kiểu int, vậy nên exception được xử lý tại đây. Và luồng thực thi chương trình được tiếp thoajtaij vi trị đánh dấu như trên code minh họa.

    Để ý tiếp, trong hàm main cũng có khối try và khối catch xử lý exception kiểu int, nhưng exception đã được xử lý tại hàm First(), vậy nên khối catch tại hàm main không cần xử lý nữa.

    Khối catch


    Mỗi khối catch phải chịu trách nhiệm xử lý 1 kiểu exception nào đó hoặc xử lý tất cả các exception.

    Ví dụ .

    C++ Code:
    1. int main()
    2. {
    3.     try
    4.     {
    5.         // Statements that may throw exceptions you want to handle now go here
    6.         throw -1;
    7.     }
    8.     catch (int) // bắt exception kiểu int
    9.     {
    10.         // Any exceptions of type int thrown within the above try block get sent here
    11.         cerr << "We caught an exception of type int" << endl;
    12.     }
    13.     catch (double)  // bắt exception kiểu double
    14.     {
    15.         // Any exceptions of type double thrown within the above try block get sent here
    16.         cerr << "We caught an exception of type double" << endl;
    17.     }
    18.  
    19.     return 0;
    20. }

    Output :

    We caught an exception of type int

    Bắt tất cả các exception :

    C++ Code:
    1. try
    2. {
    3.     throw 5; // throw an int exception
    4. }
    5. catch (double dX) // bắt exception kiểu double
    6. {
    7.     cout << "We caught an exception of type double: " << dX << endl;
    8. }
    9. catch (...) // bắt tất cả kiểu exception
    10. {
    11.     cout << "We caught an exception of an undetermined type" << endl;
    12. }

    Ta nên sử dụng catch tất cả exception trong hàm main để tránh việc chương trình bị kết thúc và ta có cơ hội lưu dữ liệu, thông báo lỗi.

    Exception và hàm thành viên

    Ta nên sử dụng exception trong các hàm thành viên, hàm khởi tạo thay thế việc báo lỗi thông qua tham số truyền bởi tham chiếu hay giá trị return. Vì khi 1 exception được ném, việc tạo đối tượng sẽ bị hủy bỏ và hàm hủy sẽ không được gọi, khác với khi báo lỗi thông qua tham số truyền bởi tham chiếu hay qua giá trị return, đối tượng vẫn được khởi tạo 1 cách không như mong muốn.

    Ta có thể sử dụng std::exception để miêu tả cụ thể lỗi gì đã xảy ra.

    Đặc biệt nên sử dụng std::exception để bắt các exception khi sử dụng thư viện chuẩn. Bởi ta chưa chắc đã biết chính xác kiểu exception sẽ được ném khi sử dụng các thư viện chuẩn.

    Ví dụ .

    C++ Code:
    1. try
    2. {
    3.     // do some stuff with the standard library here
    4. }
    5. catch (std::exception &cException)
    6. {
    7.     cerr << "Standard exception: " << cException.what() << endl;
    8. }

    Exception và thừa kế

    C++ xử lý exception theo tính đa hình trong thừa kế.

    Ví dụ .

    C++ Code:
    1. class Base
    2. {
    3. public:
    4.     Base() {}
    5. };
    6.  
    7. class Derived: public Base
    8. {
    9. public:
    10.     Derived() {}
    11. };
    12.  
    13. int main()
    14. {
    15.     try
    16.     {
    17.         throw Derived();
    18.     }
    19.     catch (Base &cBase)
    20.     {
    21.         cerr << "caught Base";
    22.     }
    23.     catch (Derived &cDerived)
    24.     {
    25.         cerr << "caught Derived";
    26.     }
    27.  
    28.     return 0;
    29. }

    Trong trường hợp này, ouput sẽ là :

    caught Base

    Vì C++ xử lý exception theo thứ tự của khối lệnh catch, khi gặp khối lệnh catch xử lý kiểu Base, vì Derived cũng là 1 Base, nên khối lệnh xử lý Base sẽ xử lý.

    Để khối catch xử lý Derived xử lý exception này, ta chỉ việc đảo vị trí các khối catch :

    C++ Code:
    1. class Base
    2. {
    3. public:
    4.     Base() {}
    5. };
    6.  
    7. class Derived: public Base
    8. {
    9. public:
    10.     Derived() {}
    11. };
    12.  
    13. int main()
    14. {
    15.     try
    16.     {
    17.         throw Derived();
    18.     }
    19.     catch (Derived &cDerived)
    20.     {
    21.         cerr << "caught Derived";
    22.     }
    23.     catch (Base &cBase)
    24.     {
    25.         cerr << "caught Base";
    26.     }
    27.  
    28.     return 0;
    29. }

    Một vài lưu ý khi sử dụng exception

    Dọn dẹp tài nguyên

    Ví dụ . Thao tác file.

    C++ Code:
    1. try
    2. {
    3.     OpenFile(strFilename);
    4.     WriteFile(strFilename, strData);
    5.     CloseFile(strFilename);
    6. }
    7. catch (FileException &cException)
    8. {
    9.     cerr << "Failed to write to file: " << cException.what() << endl;
    10. }

    Trong ví dụ trên, nếu WriteFile() thất bại, hàm này ném 1 FileException, khi đó, chúng ta đã mở file rồi, và ở đây tả chỉ thông báo lỗi mà chưa đóng file lại.

    Bởi vậy, ta nên :

    C++ Code:
    1. try
    2. {
    3.     OpenFile(strFilename);
    4.     WriteFile(strFilename, strData);
    5.     CloseFile(strFilename);
    6. }
    7. catch (FileException &cException)
    8. {
    9.     // Make sure file is closed
    10.     CloseFile(strFilename);
    11.     // Then write error
    12.     cerr << "Failed to write to file: " << cException.what() << endl;
    13. }

    Ví dụ . Cấp phát động.

    C++ Code:
    1. try
    2. {
    3.     Person *pJohn = new Person("John", 18, E_MALE);
    4.     ProcessPerson(pJohn);
    5.     delete pJohn;
    6. }
    7. catch (PersonException &cException)
    8. {
    9.     cerr << "Failed to process person: " << cException.what() << endl;
    10. }

    Ở ví dụ trên, nếu hàm ProcessPerson() ném PersonException, thì khối lệnh catch sẽ không có cách nào để giải phóng bộ nhớ mà pJohn trỏ tới, vì pJohn được khai báo cục bộ trong khối try.

    Có 2 cách để giải quyết vấn đề này :

    - Khai báo ngoài khối try.

    C++ Code:
    1. Person *pJohn = NULL;
    2. try
    3. {
    4.     pJohn = new Person("John", 18, E_MALE);
    5.     ProcessPerson(pJohn );
    6.     delete pJohn;
    7. }
    8. catch (PersonException &cException)
    9. {
    10.     delete pJohn;
    11.     cerr << "Failed to process person: " << cException.what() << endl;
    12. }

    - Sử dụng std::auto_ptr.
    C++ Code:
    1. #include <memory> // for std::auto_ptr
    2. try
    3. {
    4.     pJohn = new Person("John", 18, E_MALE);
    5.     auto_ptr<Person> pxJohn(pJohn); // pxJohn now owns pJohn
    6.  
    7.     ProcessPerson(pJohn);
    8.  
    9.     // when pxJohn goes out of scope, it will delete pJohn
    10. }
    11. catch (PersonException &cException)
    12. {
    13.     cerr << "Failed to process person: " << cException.what() << endl;
    14. }

    std::auto_ptr sẽ tự động giải phóng bộ nhớ mà nó trỏ tới khi ra ngoài phạm vi.

    Không nên sử dụng std::auto_ptr để trỏ tới 1 mảng, vì std::auto_ptr sử dụng delete để giải phóng bộ nhớ, nó không sử dụng delete[].

    std::auto_ptr không có phiên bản nào để tự động giải phóng bộ nhớ của 1 mảng, ta nên sử dụng std::vector, nó sẽ tự giải phóng khi ra ngoài phạm vi.

    Exception và hàm hủy

    Không như hàm khởi tạo, không nên ném exception trong hàm hủy.

    Nếu 1 exception được ném từ 1 hàm hủy trong quá trình unwinding thư stack, trình biên dịch không biết nên tiếp tục quá trình unwinding the stack hay nên xử lý exception mới. Kết quả là chương trình bị ngừng.

    Nên ghi log thay vì ném exception trong hàm hủy.
    Đã được chỉnh sửa lần cuối bởi luc13aka47 : 03-10-2012 lúc 01:16 PM.

Các đề tài tương tự

  1. Exception Lỗi: "The type initializer for 'PublicMethods' threw an exception."?
    Gửi bởi khatran trong diễn đàn Thắc mắc lập trình C#
    Trả lời: 1
    Bài viết cuối: 29-02-2012, 06:42 PM
  2. Structured Exception Handling (SEH) và C++ Exception Handling khác nhau như thế nào
    Gửi bởi Kevin Hoang trong diễn đàn Tutorials và Thủ thuật Visual C++
    Trả lời: 0
    Bài viết cuối: 05-01-2012, 11:24 AM
  3. ADO.NET Lấy ID của exception trong C#
    Gửi bởi kobietphaihoi trong diễn đàn Thắc mắc lập trình C#
    Trả lời: 2
    Bài viết cuối: 28-03-2011, 02:22 PM
  4. Lập trình C++ Exception handling, Không gian tên dùng để làm gì?
    Gửi bởi DingPhonh trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 0
    Bài viết cuối: 17-02-2011, 08:05 PM
  5. How to use Structured Exception Handling Anti emulators ?
    Gửi bởi bkavPro trong diễn đàn Thắc mắc lập trình Visual C++
    Trả lời: 4
    Bài viết cuối: 04-09-2010, 09:55 PM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn