try/finally implementation in c++

A

avasilev

Hi all

Its one of my little pragramming challenges to implement a try/finally
construct in C++. I have done several implementations but they all have
limitations. Have tried mostly using a destructor to execute the
"finally" code. However this has the big limitation that the destructor
cannot see local variables. Another approach that solves this problem
is the following:

{
MyBaseException* handledException = NULL;
try
{
<try code>
}
catch(MyBaseException& e)
{
handledException = e.Clone();
}

<finally code>

if (handledException)
throw *handledException;
}

However, this method has the limitation that it works only for specific
classes of exception objects.

I'm curious to see other ideas

Regards
Alex
 
N

Noah Roberts

avasilev said:
Hi all

Its one of my little pragramming challenges to implement a try/finally
construct in C++. I have done several implementations but they all have
limitations. Have tried mostly using a destructor to execute the
"finally" code. However this has the big limitation that the destructor
cannot see local variables.

Not that I am recommending it but...

It could if the destructor was part of a local struct containing those
variables.

Something to think about.
 
P

Phlip

avasilev said:
I'm curious to see other ideas

has a humongous multi-thread conversation on
this topic. Someone has suggested adding a 'finally' keyword to the
language.

I find the idea that anyone thinks C++ can learn something about cleanup
from Java very disturbing.

Most sources recommend RAII for C++'s exception-neutral cleanup mechanism.
 
H

Howard

Phlip said:
has a humongous multi-thread conversation on
this topic. Someone has suggested adding a 'finally' keyword to the
language.

I find the idea that anyone thinks C++ can learn something about cleanup
from Java very disturbing.

Could be a Pascal programmer... using try..finally was a standard way to do
things in Delphi.

But I think it's _always_ possible to learn from other languages. (Of
course, sometimes you just learn what's NOT so good, though...) :)
Most sources recommend RAII for C++'s exception-neutral cleanup mechanism.

Yep. (But it's a bit of a pain sometimes to make a new class just to handle
what would only be a couple extra lines of code (i.e., the try/finally
keywords), if C++ _were_ to implement it.) Still, I'd agree that RAII is
probably the best solution, at least currently.

-Howard
 
A

avasilev

Yes, this is something I have thought about. The problem is that it is
quite slow. Imagine that this code executes in a loop - every time it
will throw two exceptions, depending on the compiler/runtime this could
execute terribly slow.
 
B

belvis

avasilev said:
{
MyBaseException* handledException = NULL;
try
{
<try code>
}
catch(MyBaseException& e)
{
handledException = e.Clone();
}

<finally code>

if (handledException)
throw *handledException;
}

However, this method has the limitation that it works only for specific
classes of exception objects.

It also has the limitation of leaking memory from every try/catch block
whenever it catches an exception, unless you've got some mechanism for
telling a catch handler to delete the exception.

Bob
 
A

avasilev

It's true, I have programmed in Delphi a lot, but I am not trying to
make C++ behave like Delphi. One of the reasons for which I like C++ is
that it gives a lot more freedom. However, I think particularly this
issue does the opposite - limits your freedom - RAII is not universal.
Sometimes it is hard/ugly to encapsulate everything that has to be
cleaned up in one class, so that the destructor can access it. For
example - when some of the resources are already encapsulated more
appropriately in other classes. Then the solution would be either to
redesign the whole class structure artificially so that RAII can be
applied, thus screwing up the design for the sake of RAII. The other
approach could be to pass references to external resources to the
object so that the constructor/destructor can acces it. So I think
forcing the use of RAII and claiming that it is the ultimate solution
in all cases is not in the C++ spirit.
 
P

Phlip

avasilev said:
So I think
forcing the use of RAII and claiming that it is the ultimate solution
in all cases is not in the C++ spirit.

I'm not sure who you reply to, but I didn't claim RAII is ultimate. I think
block closures are, but we won't see those in C++ for a while.

Consider this C++ -like code:

Foo()
{
Bar aBar;
aBar.dirty();
finally:
aBar.clean();
}

Implement 'finally' as you like - as a keyword, or a catch().

The problem with that code is it couples Bar's private internal state to
Foo. If you change how dirty Bar gets, you must find every client of Bar
and upgrade the finally block. Bar's cleaness should be a private,
encapsulated detail.
Sometimes it is hard/ugly to encapsulate everything that has to be
cleaned up in one class, so that the destructor can access it.

And sometimes that is a sign that the client function (Foo()) is doing too
much, and it needs its statements refactored into Bar classes, to
encapsulate everything about them, not just how they clean up.
 
A

avasilev

Philip, sorry for the misunderstanding. What i meant was that the
language forces you to use RAII, and indirectly (or maybe directly, I
remember reading answers to language extension proposals) the C++
standards committee thus claims it is _the_ way to do things.
I agree that the code you give as an example has this problem. In this
case RAII is more appropriate. There is the problem for exposing
internal state, but if the onterface of the resource is designed and
implemented correctly, then this should not be an issue.
Often there is not just one line of code that has to be executed in
finally, and not referring to only one resourece. There could be many
finally blocks in different parts of the application, referring to same
resources. The semantics of the blocks could no be just "cleanup",
could be more complicated - e.g. signalling events etc. Probably it
could always be implemented with RAII, but this can be a pain and can
complicate things.

Alex
 
K

Kaz Kylheku

avasilev said:
Hi all

Its one of my little pragramming challenges to implement a try/finally
construct in C++. I have done several implementations but they all have
limitations.

An unwind protect mechanism is architectural. If it's not in the
substrate, it may be impossible to implement 100%. There are semantic
pieces in C++ that could do the job, but the syntactic abstraction
isn't there.
I'm curious to see other ideas

Firstly, separate the finally mechanism from try and catch. Conflating
them together is an unnecessary Java thing which you don't have to
adopt. In some other languages, the mechanism that means "execute this
regardless of how this terminates" is a separate construct. Common Lisp
calls it unwind-protect, a term that is sometimes used outside of Lisp
to refer to any such a mechanism..

If you separate it out, you have a simpler job, because your syntax
does not have to incorporate catch blocks for exceptions. It has only
two pieces: the block that is protected, and the code that is run
unconditionally.

The problem is that you need the unconditional code in two places:
immediately following the protected code (normal return case) and in a
catch (...) handler.

To do this, you can make the entire code into a giant macro argument,
by doing which you end up with something that combines ugly appearance
with debugger hostility:

#include <iostream>

#define ugly_unwind_protect(block, cleanup) \
try { block } catch (...) { cleanup throw; } cleanup

int main()
{
int x = 3;

try {
ugly_unwind_protect (
{
throw 42;
},
{
// demonstrate access to locals
std::cout << "x = " << x << std::endl;
}
)
} catch (int ex) {
// the 42 is caught here
std::cout << "ex = " << ex << std::endl;
}

return 0;
}

Oh yeah, because statements are passed as a single macro argument,
commas in them can cause problems. Try changing it to "throw 0, 42" and
see, hahaha. That's only a problem for commas that are not enclosed in
parentheses.

As you can see, you can get all the functionality of try/catch/finally
with try/catch and a separate unwind-protect.

Note how the unwind code is duplicated textually in two places. Yuck!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,431
Messages
2,571,679
Members
48,796
Latest member
Greg L.

Latest Threads

Top