Jump to content
  • Advertisement
Sign in to follow this  
yacwroy

C++: Exception Handling - How to avoid leaks.

This topic is 3637 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm quite new to Exception Handling. I resisted the idea for a long time because it seems to me that it is easy to overlook memory leaks and other state issues when exceptions are thrown. I'm not really sure that I believe try/throw/catch is better than returning error codes, but I'm trying it out. My general question is: If a function is partway through creating a collection of objects on the heap when an exception is thrown, is there a better way of handling this then simply writing try/catch blocks around every single function call that can throw so that I can clear my heap-allocated objects before I throw-exit out of the function? I guess this is still more efficient than checking return codes, but it's far bulkier to code. My current ideas are: 1. Design the function so that all heap objects are pointed to by a function-local object that will destroy them (such as my heap-allocated token tree below having a function-local stack-allocated root). 2. Create some sort of pointer template class that deletes on destruction, and clear it before a natural function exit. (Seems like an ugly hack to me). 3. Wrap the function in a single try/catch block. In the catch block free all loose objects, then re-throw. This is my current line of thought, although it places many restrictions on my code, and requires most of my functions to have try/catch blocks. The current scenario is: In my program's UI you can open a file. This file is opened using ifstream, parsed using a linear recursive descent parser, and converted to data. If any of these components fails (eg the file doesn't exist, or the data doesn't fit the grammar), I'm throwing an error (or ofstream is via std::ofstream::exception()). The program catches the error and displays a popup on the UI, but doesn't exit the program. Now, in my parser I'm building a tree of tokens (unlimited recursion). Each token object can have unlimited children, and destroys them in the event of it's own destruction. Each time a token is read and the parser ascends (toward the root), it is either deleted or added to the token below it. There is a vector of pointers to tokens, one for each depth in the tree at the current point in the file being parsed. This means, if I'm 5 tokens deep, I have 5 tokens on the heap that are currently only referred to by my vector of pointers. After parsing completes, all parsed tokens have either been discarded and deleted, or are part of the final tree. My current thinking is to add the heap-allocated root token to a function-local stack-allocated super-root token, have every token currently being parsed added to it's parent (whether or not it is actually accepted), and adding a method to tokens that will orphan the last added token (or a specific token). This way, in the event of a throw, the super-root token will delete itself and it's child root token, which will then proceed up the tree deleting all tokens as usual. If the parser is successful, the root token is orphaned by the super-root token, and is returned. Either this or a try/catch block around the whole function with some code to delete all members in the vector. Actually, the latter seems a better idea. However, this isn't the only problem (others are constructing objects in a scenario from a file, loading models, converting models), and I'm looking for a general way to avoid this issue. ========================================================== I also have a couple more generic questions on exceptions: 1. Is it good practice to end a try/catch block with an empty (catch all) catch, or should you let unknown exceptions halt the program? 2. Is it possible to throw across DLL or module lines? Eg, a DLL throws and the main program catches, or vice versa. 3. What happens if you throw from a secondary thread and it isn't caught within the thread's entry function. ============================ Thanks for all assistance.

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by yacwroy
I'm quite new to Exception Handling. I resisted the idea for a long time because
My current ideas are:
1. Design the function so that all heap objects are pointed to by a function-local object that will destroy them (such as my heap-allocated token tree below having a function-local stack-allocated root).
2. Create some sort of pointer template class that deletes on destruction, and clear it before a natural function exit. (Seems like an ugly hack to me).
This already exists in C++: std::auto_ptr
By using these kinds of RAII objects you can usually avoid try/catch blocks altogether.

Quote:
I also have a couple more generic questions on exceptions:
1. Is it good practice to end a try/catch block with an empty (catch all) catch, or should you let unknown exceptions halt the program?
2. Is it possible to throw across DLL or module lines? Eg, a DLL throws and the main program catches, or vice versa.
3. What happens if you throw from a secondary thread and it isn't caught within the thread's entry function.
1) No. Doing that will silently throw away valid exceptions without handling them. If you can't handle an exception then you shouldn't be catching it.
(If no-one catches the exception, that means no-one knew how to handle it, so it's fair that the program crashed ;))

2) I'm not sure. I think it's undefined behaviour...
However, I've used a framework before that allowed me to throw an exception on a network client and catch it on the server -- So if it isn't possible, I'm sure someone's published a work-around.

3) AFAIK, the thread is terminated, which is bad (just like if the main thread fails to catch an exception).
The framework that I mentioned above also had a mechanism for catching exceptions in the thread-main function and posting them back to the main thread somehow. Don't ask me how this was implemented tho ;p

[Edited by - Hodgman on July 9, 2008 1:48:54 AM]

Share this post


Link to post
Share on other sites
Quote:

Quote:
Quote:
Original post by yacwroy
I'm quite new to Exception Handling. I resisted the idea for a long time because
My current ideas are:
1. Design the function so that all heap objects are pointed to by a function-local object that will destroy them (such as my heap-allocated token tree below having a function-local stack-allocated root).
2. Create some sort of pointer template class that deletes on destruction, and clear it before a natural function exit. (Seems like an ugly hack to me).


This already exists in C++: std::auto_ptr


Hmm, I wouldn't really recommend auto_ptr. Auto_ptr has weird copying, the act of copying an auto_ptr sets the original source of the copy to NULL. I would take a look at boost::shared_ptr instead (or if your compiler supports it already std::tr1::shared_ptr which is the standardized version based on the boost one) which are both complete reference counted smart pointers.

If you use auto_ptr you cannot store them in standard containers and expect them to work, shared_ptrs however can be stored in containers and work correctly.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kazade
Hmm, I wouldn't really recommend auto_ptr. Auto_ptr has weird copying, the act of copying an auto_ptr sets the original source of the copy to NULL. I would take a look at boost::shared_ptr instead (or if your compiler supports it already std::tr1::shared_ptr which is the standardized version based on the boost one) which are both complete reference counted smart pointers.


You cannot recomment shared_ptr over auto_ptr in the general case, simply because the two have different purposes. The former is used to represent shared ownership semantics (which the latter cannot do), while the latter is used to represent exclusive ownership transferral (which the former cannot do).

For the purposes of cleaning up after yourself during stack unwinding (exception-friendly RAII at function scope), the best alternative is scoped_ptr which is designed to represent exactly that, although it's true that auto_ptr can be used as a cheap replacement when the boost libraries are not available.

Share this post


Link to post
Share on other sites
maybe i am just anal - but at least for personal code, i dont use catch handlers for doing resource management such as deleting allocated memory. instead everything gets treated as raii (memory, files, threads, network/db connections). this makes sense from the point of view of encapsulating functionality. catch handlers are then distinguished for their value in dealing with more high level state stuff/logging which is normally far away from the throw point.

Share this post


Link to post
Share on other sites
Thanks all for your replies. ++:).

Ok RAII sounds like a good thing.

'shared_ptr' sounds a little bulky for what I need, and I prefer the idea of unique ownership.

'scoped_ptr' would be the best, but it doesn't have much over 'auto_ptr' and it requires boost. This is just slightly more annoying since I plan to have plugins which would require the end user to install boost. So, is the minimal annoyance worth the minimal benefit.

I think I'll try using 'auto_ptr' for now. Ownership transfer via copy is actually kinda useful, returning the pointer prevents destruction etc.

I wonder, does using exceptions increase program efficiency? Less return-code checks, but features like RAII have overheads too.

Share this post


Link to post
Share on other sites
exceptions have a certain amount of over head, i've always been curoius as to exactly how much, but from what I understand they are certainly *not* more efficient.

Still they are incredibly useful, and in all but the most extreme performance cases, they create much more manageable code that's well worth the slight performance hit.

Share this post


Link to post
Share on other sites
Quote:
Original post by yacwroy
'scoped_ptr' would be the best, but it doesn't have much over 'auto_ptr' and it requires boost. This is just slightly more annoying since I plan to have plugins which would require the end user to install boost. So, is the minimal annoyance worth the minimal benefit.


The pointer library in Boost is a header-only library, as is most of Boost. This means no 'installation' as such is required. Just get the header files, point your compiler/IDE to the right spot, and include <boost/scoped_ptr.hpp>, and you're done. Other ridiculously helpful header-only libraries include Boost.Function, Boost.Bind, and Boost.LexicalCast.

Share this post


Link to post
Share on other sites
IIRC, exceptions have a large amount of overhead only WHEN they are thrown. But typical code execution doesn't throw.

If the code doesn't cause an error then (either using return codes or exceptions), it'd just be run some logical test, then one jump-if-error-bit-is-set or similar. And if the code throws, hopefully nobody cares too much about efficiency. But there is an additional test required if return codes are used (after the returning function exits).

The exception handling code may be far away so as to not clutter up your RAM pages (just my guess - that's how I'd do it), wheras the returned error handling code could be located anywhere. My guess is that the exception handling code would typically be far larger, making for larger files, but well out of the way and unlikely to end up causing performance reduction.

However that's mostly speculation.




Share this post


Link to post
Share on other sites
Ideally everything is done using RAII / smart-pointers, and you can throw an exception from anywhere that gets caught in what could be your only catch block in main, and upon entering the catch block, ALL memory allocated everything else on the stack has already been freed automagically.

In other words: To leverage the true power of exception handling you must first master RAII.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!