Exceptional Safety
Delphi is exception safe language. That means well written code can recover from the most
horrible exceptions, including the dreaded out of memory error, and continue running in a
perfectly operational state - as if nothing bad happened. Of course, that is a feature that
Delphi provides, but your code and application eventually must determine logic at which point
raised exception is recoverable and at which point it is not.
For instance, if you allow user to open some external file for further processing and your code trips on out of memory exception because the file is too large to process, you can fully recover from such event, show error message to the user "Sorry, selected file is too big" and application can merrily continue working. On the other hand if you trigger out of memory error during some operation your application absolutely must be able to perform, you can decide that the best course of action is terminating the application - after you show appropriate error message and perform whatever shutdown procedure is required.
It is about pointing to one often forgotten and ignored fact that can crush all your exception handling efforts: object destruction process must not cause or raise any unhandled exceptions or you will have memory leaks beyond your ability to fix them.
Particulary, that means the
Same applies to the
Proper handling of any exceptions inside
Even code using interfaces and automatic memory management will cause memory leaks if exceptions break destruction process.
Assuming that following classes cause unhandled exceptions in
Not every single line of code is exception prone. You just have to protect code that really can cause an exception and not all of it.
Following destructor code is perfectly fine. No need to handle any exceptions there.
Of course, if
For instance, if you allow user to open some external file for further processing and your code trips on out of memory exception because the file is too large to process, you can fully recover from such event, show error message to the user "Sorry, selected file is too big" and application can merrily continue working. On the other hand if you trigger out of memory error during some operation your application absolutely must be able to perform, you can decide that the best course of action is terminating the application - after you show appropriate error message and perform whatever shutdown procedure is required.
One little thingy
Focus of this post is not teaching you how to write exception cleaning and handling code and how and when to usetry...finally
and try...except
blocks.It is about pointing to one often forgotten and ignored fact that can crush all your exception handling efforts: object destruction process must not cause or raise any unhandled exceptions or you will have memory leaks beyond your ability to fix them.
Particulary, that means the
BeforeDestruction
method - and destructors themselves - must
never ever allow exceptions to escape them. Any escaping exception there will always cause
memory leak. Period.Where does it leak?
Unhandled exception in any destructor will not only break calling inherited destructors chain (if there are any), but more importantly it will skip callingFreeInstance
method that
is automatically called after the destructor chain and is responsible for cleaning up instance
managed fields and releasing its memory allocated on the heap. If FreeInstance
does not run,
your code will leak that object instance's memory.Same applies to the
BeforeDestruction
method - unhandled exception there will skip calling
the whole destructor chain as well as FreeInstance
.Proper handling of any exceptions inside
BeforeDestruction
method or destructors implies
that you must make sure that all code that is responsible for any kind of cleanup, including
calling inherited methods, that absolutely must be executed is executed during that exception
handling process.But, I have try...finally
No amount of dancing around broken destructors (orBeforeDestruction
) will fix the memory
leak. That is just wishfull thinking. The only way to fully and properly solve such leaks is
to fix broken destructors (or BeforeDestruction
) from within.Even code using interfaces and automatic memory management will cause memory leaks if exceptions break destruction process.
Assuming that following classes cause unhandled exceptions in
BeforeDestruction
or in any of
the destructors all following code will result with memory leaks.
var
Broken: TBroken;
begin
Broken := TBroken.Create;
try
finally
Broken.Free;
end;
end;
var
Broken: IBroken;
begin
Broken := TBrokenAutomatic.Create;
end;
Please, don't
If you are tempted to go around your code eating up all exceptions withtry...except
blocks
in every destructor you have ever written, then please don't.Not every single line of code is exception prone. You just have to protect code that really can cause an exception and not all of it.
Following destructor code is perfectly fine. No need to handle any exceptions there.
destructor TFoo.Destroy;
begin
FBar.Free;
inherited;
end;
Of course, if
FBar
destructor is broken then TFoo
destructor will also be broken, but the
proper place to handle potential exception is in TBar
destructor itself. Adding
try...except
code around FBar.Free
will accomplish absolutely nothing because if TBar
destructor is broken, TBar
object instance will leak regardless. Yes, eating up exception in
TFoo
destructor would at least prevent leaking of TFoo
instance, but if it is broken does
it really matter how much?
Comments
Post a Comment