Có nên dùng try catch trong c không

Xử lý ngoại lệ - exception handling hiệu quả với try, catch, throw thông qua ví dụ trong C++.

Mọi đoạn chương trình được viết ra đều tiềm ẩn khả năng sinh lỗi. Có thể là lỗi chủ quan do lập trình sai hoặc có thể là lỗi khách quan do dữ liệu hay trạng thái của hệ thống (thiếu memory, mạng bị corrupt …). Không dễ dàng để kiểm soát tất cả trường hợp của chương trình, đặc biệt các lỗi liên quan đến số học, bộ nhớ, … Vì thế, việc giải quyết các ngoại lệ để ứng dụng chúng ta tránh được những rủi ro là 1 điều không kém phần quan trọng.

Tổng quan

Exception – Ngoại lệ là cơ chế thông báo và xử lý những vấn đề tiềm ẩn phát sinh (giống như lỗi runtime) trong chương trình bằng cách chuyển quyền điều khiển đến những hàm đặc biệt nhằm tách phần xử lý lỗi ra khỏi thuật toán chính.

Một ngoại lệ là một đối tượng chứa thông tin về lỗi và truyền thông tin của lỗi cho bộ phận xử lý để có những hướng giải quyết phù hợp.

Ngoại lệ có thể thuộc bất kì kiểu dữ liệu bất kỳ của C++:

  • Có sẵn như int, char, int* , char* ,…
  • Kiểu người dùng tự định nghĩa.
  • Các lớp ngoại lệ trong thư viện

Cơ chế của ngoại lệ

Cơ chế

Quy trình gọi hàm và trả về trong trường hợp bình thường:

Có nên dùng try catch trong c không

Quy trình ném và bắt ngoại lệ tại member_function():

Có nên dùng try catch trong c không

Cơ chế xử lý ngoại lệ của C++ có 3 tính năng chính:

  • Khả năng tạo và ném ngoại lệ (sử dụng từ khóa throw).
  • Khả năng bắt và giải quyết ngoại lệ (sử dụng từ khóa catch).
  • Khả năng tách phần xử lý ngoại lệ ra khỏi phần có thể sinh lỗi (sử dụng từ khóa try).

Hướng giải quyết

Muốn bắt exception tại vị trí nào của chương trình thì ta phải đặt tại đó 1 đoạn code nhằm kiểm tra ngoại lệ. Đoạn chương trình này được đóng gói trong 1 khối try-block. Khi một ngữ cảnh thực thi tiếp nhận và truy nhập một ngoại lệ được coi là bắt ngoại lệ (catch the exception) thì nó được truyền đến phần xử lý ngoại lệ (exception handler). Song, nếu không xảy ra bất cứ ngoại lệ nào thì chương trình được build bình thường và các bộ xử lý đều được bỏ qua.

Và quá trình truyền ngoại lệ từ ngữ cảnh thực thi hiện hành đến mức thực thi cao hơn - được gọi là ném 1 ngoại lệ (throw an exception) – bằng cách sử dụng từ khóa throw. Phần xử lý ngoại lệ được khai báo trong từ khóa catch dùng để xử lý ngay khi sau try-block.

Ví dụ đoạn chương trình sau:

// Exceptions
#include 
#include 
using namespace std;

int main()
{
	try
	{
		throw string("String!");
	}
	catch (string e)
	{
		cout << "An exception occurred. Exception: " << e << '\n';
	}
	catch(...)
	{
		cout << "An exception occurred." << '\n';
	}
	return 0;
}

Đoạn code dưới xử lý ngoại lệ bên trong khối lệnh try. Ở ví dụ trên chỉ đơn giản là ném 1 ngoại lệ:

throw string("String!");

Biểu thức ném ngoại lệ chấp nhận 1 tham số(trong trường hợp này là 1 chuỗi kí tự “String!”)  được thông qua để xử lý ngoại lệ.

Đoạn xử lý ngoại lệ được khai báo vời từ khóa catch ngay sau kết thúc khối try. Cú pháp bắt ngoại lệ cũng tương tự như khai báo 1 hàm thông thường với 1 tham số. Loại tham số truyền vào catch rất quan trọng vì các loại của đối số này thông qua biểu thức ném để kiểm tra và chỉ khi phù hợp thì các ngoại lệ được bắt mới được xử lý.

Khi ngoại lệ được ném đi, hệ thống xử lý ngoại lệ sẽ kiểm tra các kiểu được liệt kê trong khối catch theo thứ tự liệt kê:

  • Khi tìm thấy kiểu đã khớp, ngoại lệ được coi là được giải quyết, không cần tiếp tục tìm kiếm.
  • Nếu không tìm thấy, mức thực thi hiện hành bị kết thúc, ngoại lệ được chuyển lên mức cao hơn.

Do khó có thế kiểm soát mọi trường hợp xảy ra trong chương trình và để mức thực thi hiện hành không bị kết thúc giữa chừng ta có thể sử dụng dấu “…” làm đối số truyền vào catch, phần xử lý này sẽ bắt bất kỳ ngoại lệ nào được ném (bao gồm cả những ngoại lệ không thể giải quyết).


View Full Version : Khi nào nên dùng try...catch trong lập trình C#



zkday2686

22-12-2008, 06:43 PM

Chào các bạn. nay rảnh rổi ngồi nghĩ bậy bạ nghĩ ra 1 câu hỏi, nó cũng điên điên nhưng đưa lên đây anh em cùng thảo luận. (\'o)

Trong bộ .NET nó cung cấp cho chúng ta một cái tool dùng để chặn các 'Ngoại lệ' lúc runtime rất hay đó là try...catch.

Nhưng zkday được biết dùng nó thì làm chương trình chậm đi

Vậy vấn đề đặt ra ở đây là khi nào chúng ta cần dùng nó. khi nào không (:P)


trung

22-12-2008, 07:33 PM

hê hê mình thấy là dùng trong kết nối database là nhiều nhất bởi khi kết nối database có rất nhiều lỗi phát sinh (mình kiến thức còn nông cạn có gì sai chỉ giáo thêm :D)


vf6.0

22-12-2008, 11:06 PM

mình nghĩ khi nào mình cảm thấy đoạn code có khả năng phát sinh lỗi thì mình sẽ dùng nó để bắt lỗi hoặc khi kết nối với database thì mình nên dùng nó để có thể xử lý lỗi của database theo ý mình chứ đừng để chương trình tự xử lý.
Nếu bạn nào là cao thủ SQL thì nên dùng trigger để bắt lỗi thì tốt hơn là mình dùng try... catch.Vì dùng try... catch sẽ có thời gian xử lý lâu hơn là trigger.Đối với những chương trình nhỏ thì thời gian xử lý thì chẳng đáng là bao nhưng khi gặp chương trình lớn hoặc database lớn thì thời gian xử lý ảnh hưởng đến tốc dộ chạy của chương trình.
Ý kiến của mình là thế anh em đóng góp thêm nha


darkan

22-12-2008, 11:44 PM

@ vf6.0 : trigger is evil ^^! Khi bạn dùng trigger, bạn đã đẩy quá trình kiểm soát lỗi xuống server. Vừa tăng tải server, vừa góp phần gây nghẽn đường truyền ^^! Những lỗi do người dùng nhập hoàn toàn có thế kiểm soát trên client ^^!

Hơn nữa,try ... catch để bắt exception. Exception có thể do người dùng tự sinh ra (throw exception) hoặc do lỗi run time. Nếu bạn dùng trigger bạn sẽ xử lý thế nào nếu đường truyền hỏng hoặc db chưa được start?

Vì dùng try... catch sẽ có thời gian xử lý lâu hơn là trigger
Phát biểu hoàn toàn cảm tính ^^! Về cơ bản trigger giống như hàm được built-in trong db. Đã được compile sẵn nên chạy rất nhanh. Tuy nhiên để đến được cái bước ấn dữ liệu xuống db cho trigger chạy thì bạn phải làm đủ các bước get dữ liệu trên client. Trong khi đó nếu bạn dùng exception, chỉ cần lỗi 1 dữ liệu, server chả phải làm gì sất. Vậy cái nào sẽ nhanh hơn?

Và nữa là nếu bạn không có try.. catch thì khi có exception nó sẽ out ngay lập tức chứ không có chiện tự xử lý đâu ^^! M$ chưa có làm cái compiler thông minh đến như thế được đâu ^^!

@ trung : try... catch được ứng dụng rất nhiều, không phải chỉ với db bạn nhé. Nếu bạn đọc qua java sẽ thấy try...catch được dùng trong hầu hết các hàm của java.

@ zk : Tự đọc nhé ^^!

http://msdn.microsoft.com/en-us/library/aa664733(VS.71).aspx
http://msdn.microsoft.com/en-us/library/0yd65esw.aspx

Làm nhiều việc hơn thì phải chậm hơn chứ sao ^^! And do it for safe ^^!

Cheers!

P/S: Có một số trường hợp không cần dùng, ví dụ như các hàm private thì thường có try ... catch cũng được,không có cũng chẳng sao ^^!


zkday2686

23-12-2008, 12:32 AM

@trung: ngoài cái vấn đề này, còn gì nữa không nhỉ ?

@vf6.0: ngoài những điều anh Dark nói thì mình xin bổ sung một số ý kiến sau.
- Try catch là để bắt lỗi trên client. Giả sử (cứ giả bộ chương trình của bạn chỉ run trên 1 máy thôi nhé :) ) thì có 2 trường hợp:
+ Nếu bạn không viết gì liên quan tới database (giả sử là 1 cái game cờ vua đơn giản chẳng hạn :) ) thì cái vấn đề của bạn không giải quyết được rồi :).
+ Nếu bạn viết chương trình liên quan tới Database thì: bạn dùng trigger để bắt lỗi thì thiếu nhỉ. Giả sử trường hợp này nhé. Khi người dùng không kết nối được với database thì sao nhỉ ? lúc này chưa làm gì tới database để làm việc với trigger cả. :)

@dark: Thank anh;).

Mà hình như anh hiểu nhầm ý của em thì phải :).
Ý của em là: Trong những trường hợp nào chúng ta cần dùng try....catch. (tức hạn chế tối đa dùng nó)


darkan

23-12-2008, 07:26 AM

@ zk : 2 cái link anh đưa trên là nguyên lý của try... catch.

Khi code có thể xảy ra một số lỗi nằm ngoài ý muốn, nên nếu muốn hạn chế chương trình crash thì tất cả đều nên dùng try catch ^^!

Tuy nhiên các hàm private thì đều phải gọi qua các hàm public, cho nên nếu các hàm public đã dùng try catch thì exception của hàm private đều được catch ở hàm public. Nên có dùng try catch ở các hàm private hay ko ko ảnh hưởng lắm đến chương trình, dùng hay ko đều được cả ^^!

Túm lại là cái nào là public thì nên dùng try catch ^^!

Cheers!


trung

23-12-2008, 09:14 AM

trong C# bật try... catch hay ko cũng dc , chứ trong Java mà ko bật try... catch thì khi compile là bị lỗi ( thật khó hiểu )
=> try... catch đúng là dùng trong rất nhiều trường hợp ( khi cảm thấy có lỗi thì bật try... catch), nhưng đa số thường thấy khi kết nối database ( vì toàn lỗi trên trời dưới đất khó kiểm soát :o)
=>
Túm lại là cái nào là public thì nên dùng try catch ^^! câu này chính xác ( lý do : exception thường được truyền ra ngoài assembly
boundaries)
==> có gì ko chính xác nhờ các huynh chỉ giáo thêm :)


trung

23-12-2008, 09:30 AM

tiện thể cho mình hỏi : thread (nghĩa tiếng anh là sợi chỉ) mình gặp hoài nhưng ko hiểu thuật ngữa nó gọi là gì , và ứng dụng làm gì ,giải thích thêm giùm mình :o
thanks nhiều


meoconlongvang

23-12-2008, 11:17 AM

tiện thể cho mình hỏi : thread (nghĩa tiếng anh là sợi chỉ) mình gặp hoài nhưng ko hiểu thuật ngữa nó gọi là gì , và ứng dụng làm gì ,giải thích thêm giùm mình :o
thanks nhiều

Thread có thể tạm hiểu là một luồng. Về bản chất nó là một hàm nhưng có khả năng chạy song song với các hàm khác. Khi bạn gọi hàm thì bạn phải chờ nó chạy xong thì bạn mới có thể chạy tiếp. Còn nếu tạo thread thì hàm đó sẽ chạy song song và bạn ko cần phải chờ nó trả về mà vẫn chạy tiếp được.

PS : lần sau bạn ko được hỏi làm loãng chủ đề như vậy nữa nha.


zkday2686

23-12-2008, 12:28 PM

Hix thank anh :) hôm qua em đã đọc 2 cái link anh đưa nó giải thích cho em được câu nói này của anh ;)

ví dụ như các hàm private thì thường có try ... catch cũng được,không có cũng chẳng sao ^^!

tức giờ ta bỏ được 1 trường hợp phải dùng try...catch là các hàm private rồi. :D

Vì: Trước sau gì hàm private cũng được 1 hàm nào đó gọi lại. Nếu hàm private gặp Exception thì nó sẽ 'quăng' ra catch của hàm public gọi nó. :) --> tiết kiệm được 1 vài chổ try....catch. :D


NamVoDang

23-12-2008, 08:01 PM

Hầu như mấy cái library đều có exception của riêng nó, mình dùng thì mình phải catch chứ chẳng nhẽ để nó crash ra đấy à???. Ví dụ std có quả std::exception khỏi nói rồi, Ogre cũng có Ogre::Exception, CEGUI cũng có CEGUI::exception, OIS cũng có OIS::exception, mình dùng thì mình phải catch chứ.

Vì: Trước sau gì hàm private cũng được 1 hàm nào đó gọi lại. Nếu hàm private gặp Exception thì nó sẽ 'quăng' ra catch của hàm public gọi nó. --> tiết kiệm được 1 vài chổ try....catch.
Thiết kế tốt là phải try ... catch duy nhất một phát từ đầu chương trình tới cuối chương trình, ngay trong thân hàm main, vì thường một khi mà đã phải throw exception thì có nghĩa là lỗi nghiêm trọng -> chương trình không thể hoạt động đúng nữa, phải dừng ngay lập tức(sau khi đã mần một số thứ, đại để như sorry, but ..... và send cái throw message qua e-mail cho mình chẳng hạn). Còn library chỉ biết throw và throw thôi.

VD:

int main()
{
try
{
....
return 0;
}
catch(...)
{
}
V.v...
return 1;
}


Gì chứ nói lý thuyết, học thuật thì tui "rất bị" dỡ. Thấy cái article này trên CodeProject, rất hay, anh em đọc và "bình lựng": http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx


kidkid

25-12-2008, 10:19 AM

Cứ chỗ nào dễ phát sinh lỗi thì dùng try catch, tuy nhiên theo em nghĩ nếu có thể tự bắt được thì nên làm không lạm dụng try catch nó sẽ làm chậm chương trình

vd:
int a,b; // user input
int res = a/b;

Chỗ này có thể bắt lỗi b = 0 được, nên bắt không dung try catch.


NamVoDang

25-12-2008, 06:25 PM

Gì chứ nói lý thuyết, học thuật thì tui "rất bị" dỡ
Tớ tự học nên tất cả mớ lý thuyết đó tớ đều rút ra từ thực nghiệm, không phải lý thuyết suông. Cả việc try catch một lần duy nhất trong thân hàm main cũng là học tập của bọn thiết kế Ogre.

Cứ chỗ nào dễ phát sinh lỗi thì dùng try catch, tuy nhiên theo em nghĩ nếu có thể tự bắt được thì nên làm không lạm dụng try catch nó sẽ làm chậm chương trình

vd:
int a,b; // user input
int res = a/b;

Chỗ này có thể bắt lỗi b = 0 được, nên bắt không dung try catch.
Ví dụ của cậu thì tất nhiên phải kiểm tra b chứ không thể try catch được, nhưng trong chương trình của chúng ta thử hỏi một câu chỗ nào trong chương trình không thể phát sinh lỗi?. Nhất là khi chúng ta dùng những library không phải do chúng ta viết ra, chương trình càng lớn thì càng có thể throw tại bất cứ chỗ nào, và chẳng phải nó luôn throw ngay khi ta chạy lần đầu tiên. Việc dùng try catch ngay trong thân hàm main sẽ bao quát được cả chương trình và không phải chỗ này, chỗ kia try catch.

Đây là file Main.cpp của dự án VHFOS mình đang tham gia.

#include
#include

int main(int argc,char* argv[])
{
try
{
Lotus::Engine::Create();
if(Lotus::Engine::getInstance().init())
{
//! Load load resource state
Lotus::StateManager::getInstance().createState("LoadResourceState", "LoadResource", Ogre::ST_GENERIC);
Lotus::StateManager::getInstance().changeState("LoadResource");

Lotus::Engine::getInstance().loop();

Lotus::Engine::Destroy();

return 0;
}
}
catch(SquirrelError& e)
{
SquirrelVM::PrintFunc(SquirrelVM::GetVMPtr(), L"Squirrel exception:%s", e.desc);
}
catch(Ogre::Exception& e)
{
MessageBoxA( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}
catch(CEGUI::Exception& e)
{
MessageBoxA( NULL, e.getMessage().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}
catch(OIS::Exception& e)
{
MessageBoxA( NULL, e.eText, "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}
catch(std::exception& e)
{
MessageBoxA( NULL, e.what(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}
return 0;
}

Và khi chạy game thì chả thấy bị sụt đi FPS nào ngay cả khi load cả một cái terrain to đùng vào. Với chương trình nhỏ, tỉ mỉ có thể thấy được sự chênh lệch, nhưng khi nó ăn nhiều CPU và ngốn một đống RAM thì sự chênh lệch đó chả có nghĩa lý gì so với việc đang chơi thì tưng một phát, chương trình bị crash còn gamer thì chả hiểu mô tê gì và lập trình viên cũng chả hiểu gì luôn ngoài việc thỉnh thoảng tôi đang chơi thì bị lỗi, bị lỗi và bị lỗi, chưa kể còn gây ức chế cho user vì chương trình kết thúc một cách đầy bạo lực mà chả có lời giải thích cũng như xin lỗi hay chỉ dẫn nào từ phía lập trình viên.

Spam: Chiều nay vừa thi xong hai môn Hóa, Sử hô hô.


kidkid

25-12-2008, 06:37 PM

Với các hàm catch trên theo kid NVD chỉ cần dùng assert để tiện cho debug.

Còn lại ném chung vào 1 catch thông báo cho gamer.

Trở lại vấn đề chính, ý zkday nói là khi nào dùng, và khi nào không nên dùng, do đó ý của tớ là chỉ dùng khi những đoạn code đó có khả năng phát sinh lỗi cao, như kết nối database, internet, những gì chúng ta không control được mà thôi.


NamVoDang

25-12-2008, 07:27 PM

Còn lại ném chung vào 1 catch thông báo cho gamer.
Làm sao ném chung được?, thằng throw exception là thằng library chứ có phải mình đâu, mà không phải thằng nào cũng throw ra std::exception(như thằng Ogre, nó còn ghi luôn ra lỗi hàm nào, dòng nào, file nào, với những exception của thằng Ogre thì tốt nhất chỉ nên mã hóa lại rồi send cho mình qua e-mail thôi, không nên print ra cho user nó xem). Mà trường hợp nó có thể throw thì ôi thôi đủ các thể loại trên trời dưới đất.

Trở lại vấn đề chính, ý zkday nói là khi nào dùng, và khi nào không nên dùng, do đó ý của tớ là chỉ dùng khi những đoạn code đó có khả năng phát sinh lỗi cao, như kết nối database, internet, những gì chúng ta không control được mà thôi.
Ý kiến của tớ là cái gì cũng control được hết, vấn đề là mình có nghĩ tới không thôi, mà nếu mình không nghĩ tới nó throw cho một phát là tiêu T_T. Cho nên phải try tất, tin tớ đi, không chậm hơn bao nhiêu đâu. Sau này test kĩ đến mấy vẫn có thể có bug, và nếu user nó dính bug mà ta có được cái exception message như của thằng Ogre kết hợp với log nữa thì ôi thôi debug nhanh vô đối :D.


zkday2686

25-12-2008, 11:19 PM

@Nam, kid: Cảm ơn cả 2 bạn ;)

@TQN: thank anh :).

@all: hi! chúng ta cùng 'mỗ sẻ' cái link anh TQN đưa nhé :)


darkan

29-12-2008, 11:32 AM

Cái link của bác TQN rất hay nhớ ^^!

Mỗi tội nó tập trung vào handle exception nhiều hơn là dùng try catch thế nào ^^!

Trong cái link của bác TQN có 2 điểm đáng lưu ý về sử dụng try catch.

Put a single catch (Exception ex) per thread
Generic exception handling should be done in a central point in your application. Each thread needs a separate try/catch block, or you'll lose exceptions and you'll have problems hard to understand. When an application starts several threads to do some background processing, often you create a class for storing processing results. Don't forget to add a field for storing an exception that could happen or you won't be able to communicate it to the main thread. In "fire and forget" situations, you probably will need to duplicate the main application exception handler on the thread handler.

When in doubt, don't Assert, throw an Exception

Don't forget that Debug.Assert is removed from release code. When checking and doing validation, it's often better to throw an Exception than to put an assertion in your code.

Save assertions for unit tests, for internal loop invariants, and for checks that should never fail due to runtime conditions (a very rare situation, if you think about it).

Cái này có lẽ giải quyết được vấn đề đang tranh cãi của NVD ví lại kid ^^!

@NVD : Không phải cái nào cũng control được đâu (:P). Một số thứ phát sinh kiểu đang connect zô 1 cái server nào đấy thì đạp văng cái dây mạng đi chẳng hạn ^^! Lỗi kiểu ấy thì chỉ có cách dùng exception thôi, control thế nào được ^^!

@kid : Mỗi exception có nội dung riêng, message riêng. Gộp chung lại rất khó debug. Lấy luôn cái vd của NVD cho nhanh(mặc dù cách xử lý exception như thế lại dở ^^!).

Nếu catch 1 cái general exception thì khi đem cho khách hàng sử dụng, nếu có 1 cái phát sinh thì móm ^^! Có biết được lỗi ở đâu, do thằng nào gây ra đâu ^^!


OWickedFox

31-12-2008, 02:07 PM

Tránh dùng Try Catch khi viết virus :D (trích từ một câu học được "Bạn sẽ không muốn thấy con virus của bạn ngốn 2,3 chục Mb RAM, because that's stupid").

Nhưng nếu bít lợi dụng Try Catch để gài bẫy những Anti Virus Engine thì rất được hoan nghênh. Ví dụ bắt lỗi chia cho 0. :D


vBulletin® Version 4.2.2, Copyright © 2022 vBulletin Solutions, Inc. All rights reserved. Administer by Kevin Hoang