Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

alternative approach to "throw()" exception specs?

2 views
Skip to first unread message

Hillel Y. Sims

unread,
Sep 4, 2002, 5:38:02 AM9/4/02
to
Hi, I've been thinking recently of a slightly different take on
exception specs w/r/t nothrow-guaranteed code that I think has some
advantages over the use of "throw()" style exception-specs (assuming
this mechanism is really based on valid C++ code..). The idea is
fairly simple (and builds upon some previous ideas from Andrei
Alexandrescu and others that I've seen in this newsgroup relating to
intercepting exceptions during stack-unwinding), and frankly if it has
been proposed before then please just ignore me, but I wonder if
anyone else would find this idea interesting (I also wonder if it is
valid Standard C++ code..)?

Here is something I'm currently calling NoThrowGuard / NOTHROW_REGION.
The two strategic advantages vs. throw() specs are:

1) Provide similar std::unexpected()-based runtime protection for any
desired block-scope against emitting exceptions without cumbersome
exception specs in function declaration (does not even require an
entire function)
2) pthread-specific: nothrow-regions can be protected against
thread-cancellation too (since that would pretty much violate the
concept of a nothrow block as being "guaranteed to complete"). throw()
specs do not (currently) handle this important piece of the puzzle. Of
course this part can be omitted for non-pthread platforms.

NoThrowGuard.h:
---------------
#include <exception>
#include <pthread.h>

class NoThrowGuard
{
const bool m_wasHandling;
int m_origCancelState;

public:
NoThrowGuard() : m_wasHandling(std::uncaught_exception())
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,
&m_origCancelState);
}

~NoThrowGuard()
{
const bool diediedie = (std::uncaught_exception() &&
!m_wasHandling);
if (diediedie)
std::unexpected();

pthread_setcancelstate(m_origCancelState, 0);
}

private:
// prevent copying:
NoThrowGuard(const NoThrowGuard&);
NoThrowGuard& operator=(NoThrowGuard);

}; // class NoThrowGuard

#define MY_FAVORITE_JOIN_MACRO(x, y) MY_FAVORITE_JOIN_MACRO_2(x, y)
#define MY_FAVORITE_JOIN_MACRO_2(x, y) x ## y
#define NOTHROW_REGION NoThrowGuard MY_FAVORITE_JOIN_MACRO(ntg__,
__LINE__)

--------------

The intended use of this object is in any scope which is intended to
provide nothrow-guarantee for which you would like a) enforced
guarantee of that rule with unexpected/terminate on failure (ala
throw() spec, but with more flexibility), and/or b) temporarily
disabled thread cancellation during "commit"/"must complete" code.

Obj::~Obj()
{
NOTHROW_REGION;
delete m_pMyHelper; // MyHelper's dtor should not throw!
}

void f()
{
// various processing, prepare transaction
...

// non-throwing commit
{
NOTHROW_REGION;
// guaranteed non-throwing / non-cancellable commit
// operations here
}

// commit complete, continue
...
}

The exact differences between NOTHROW_REGION vs throw() are as
follows:

- If an exception that is emitted outside the NOTHROW_REGION is
unhandled, terminate() will be called without unexpected(). When using
throw() specs, unexpected() is always called, even if the exception
would be unhandled otherwise. In any event, the program will be
terminated, unless you actually use a custom unexpected-handler
routine to prevent termination.

- NOTHROW_REGION cannot properly detect a nothrow-guarantee violation
in the case that the violation occurs during already-in-progress
primary-phase exception-based stack-unwinding and the new exception is
swallowed, due to a limitation in the std::uncaught_exception()
mechanism (however, the cancellation-protection is still in effect).
If the new exception is not swallowed inside the destructor, then the
process will terminate() "normally" as per the standard effects of a
secondary exception being emitted from a destructor during
primary-phase stack unwinding. However, if the destructor swallows the
exception, the nothrow-violation will be undetected. The following is
an example of how to reproduce this loophole:

void nothrow_func()
{
NOTHROW_REGION;
throw "oops";
}

Obj::~Obj()
{
NOTHROW_REGION;
try {
nothrow_func();
}
catch (...) { // misguided attempt at providing nothrow-guarantee..
;-)
cout << "how did this happen?" << endl;
}
}

int main()
{
try {
Obj o;
throw "loophole";
}
catch (...) {}
}

However, this is no worse than the behavior of an application which
does not use exception-specs at all in the case that a
nothrow-guaranteed (by documentation) function violates that
guarantee. Personally, I believe this would be a minor negative point,
as the necessary conditions to provoke this loophole seem fairly
contrived and are easily avoided anyway.

I look forward to any comments or criticisms of this idea..

thanks,
hys

--
Hillel Y. Sims
FactSet Research Systems
hsims AT factset.com

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Alexander Terekhov

unread,
Sep 4, 2002, 11:27:47 AM9/4/02
to

"Hillel Y. Sims" wrote:

[...m_wasHandling(std::uncaught_exception())...]

I like the idea (but you should really call terminate(); NOT unexpected()
that might throw something else... and THAT WILL escape your "guard" with
cancelation left disabled, BTW), however, as you've indicated, you'd need
uncaught exceptions COUNT to make it work "in general", I guess. :-(

Another problem is that it doesn't necessarily preclude unwinding way up
to the guard destructor... And, BTW, futute_std::unwinding(this) (from
the "10 o'clock"-wish list) would be MUCH "easier" to use, I guess. ;-)

http://groups.google.com/groups?selm=3CBC5F22.76708D0%40web.de
(Subject: Re: Alexandrescu on error handling at ACCU conference 2002)

lnxmuell:/usr/terekhov # g++ -o e e.cpp
lnxmuell:/usr/terekhov # ./e
throw 0
throw 1
throw 2
throw 3
throw 4
throw 5
throw 6
throw 7
throw 8
throw 9
Okay... ENOUGH active exceptions! ;-)
caught 9
caught 8
caught 7
caught 6
caught 5
caught 4
caught 3
caught 2
caught 1
caught 0
lnxmuell:/usr/terekhov # cat e.cpp

#include <iostream>
using namespace std;

int ex_count = 0;

int foo();

struct object {

~object() { foo(); }

};

int foo()
{

int ex = ex_count++;

try {

if ( ex < 10 ) {

// Nah, I want MORE active exceptions ;-)
object obj;

cout << "throw " << ex << endl;

throw ex;

}
else {

cout << "Okay... ENOUGH active exceptions! ;-)" << endl;

}

}
catch( int ex_caught ) {

cout << "caught " << ex_caught << endl;

}

return ex;

}

int main()
{
return foo();
}

lnxmuell:/usr/terekhov #

regards,
alexander.

Hillel Y. Sims

unread,
Sep 9, 2002, 9:20:22 AM9/9/02
to

"Alexander Terekhov" <tere...@web.de> wrote in message
news:3D75E040...@web.de...

>
> "Hillel Y. Sims" wrote:
>
> [...m_wasHandling(std::uncaught_exception())...]
>
> I like the idea (but you should really call terminate(); NOT unexpected()
> that might throw something else... and THAT WILL escape your "guard" with
> cancelation left disabled, BTW),

It's kind of weird - an unexpected-handler won't work exactly the same
under
NOTHROW_REGION violation as with throw() violation. Attempting to throw
something like bad_exception will not do the usual "translation" thing
because "throw()" itself is not in effect, so it seems like it would
instead
behave just like a typical secondary exception emitted during
stack-unwinding of primary phase exception: immediate call to
terminate()
anyway (this corresponds to observed behavior from cxx 6.5 compiler
under
VMS). So this guard cannot really be escaped in this manner. OTOH, a
custom
unexpected-handler could instead be used as a sort of logging mechanism
strategy, maybe a good place to take a stack trace or something, so
maybe it
is still worthwhile to call unexpected()?

> however, as you've indicated, you'd need
> uncaught exceptions COUNT to make it work "in general", I guess. :-(

That would certainly be cool, but I guess it's not really a big problem
anyhow because the code structure necessary to invoke the small loophole
is
fairly "bogus", as it involves attempts to swallow unknown exceptions
from
nothrow-guaranteed code anyhow in a misguided attempt to "enforce"
exception-safety, and is easily spotted and avoided:

SomeBadType::~SomeBadType()
{
// "I don't want my destructor to throw exceptions,
// and I know this function says it does not throw
// but I better swallow any exceptions just in case!
try {
cleanup_func_does_not_throw();
}
catch (...) { }
}

where
void cleanup_func_does_not_throw()
{
NOTHROW_REGION;
throw "oops..";
}

The loophole is only triggered if the destructor code is structured in
such
a manner anyway (and even then, only if the violation occurs during
primary-phase stack unwinding, when uncaught_exception is already "true"
on
the way in.. :-P). If you use "throw()" there is no such loophole -- the
nothrow violation will invoke unexpected() regardless of the catch(...)
handler. However, that is just a bogus way to write a destructor and a
more
reasonable structure, like the following, will just not be subject to
the
loophole anyhow, and so NOTHROW_REGION will provide the same level of
protection as throw():

SomeBetterType::~SomeBetterType()
{
NOTHROW_REGION; // for extra-paranoid protection!
// "I know this function cannot throw and it doesn't even
// matter if it does because of my NOTHROW_REGION
// protection, so there's just no point in using try/catch here"
cleanup_func_does_not_throw();
}

It just seems like a convenient alternative to throw() specs (plus it
gives
me cancellation protection for pthreads, which throw() will not do).

(BTW I guess this is broken on MSVC 6.0 which doesn't do throw() specs
or
uncaught_exception() very well..)

>
> Another problem is that it doesn't necessarily preclude unwinding way up
> to the guard destructor...

Hmm.. throw() doesn't necessarily seem to be any different here:

$ build f.cxx
Compaq C++ V6.5-030 for OpenVMS Alpha V7.3
- compiling F.OBJ...
- linking F.EXE...
$ r f.exe
~A
%CXXL-F-TERMINATE, terminate() or unexpected() called
%TRACE-F-TRACEBACK, symbolic stack dump follows
image module routine line rel PC abs
PC
F 0 0000000000021598
0000000000031598
F 0 00000000000216A4
00000000000316A4
F F foo 6540 00000000000001A8
00000000000301A8
F F main 6547 0000000000000244
0000000000030244
F F __MAIN 0 0000000000000070
0000000000030070
F 0 0000000000028F88
0000000000038F88
0 FFFFFFFF802533F4
FFFFFFFF802533F4
$ type f.cxx


#include <iostream>
using namespace std;

struct A
{
~A() { cout << "~A" << endl; }
};

void foo() throw()
{
A a;
throw "xyz";
}

int main()
{
try {
foo();
}
catch (...) {
cout << "caught" << endl;
}
}

>And, BTW, futute_std::unwinding(this) (from
> the "10 o'clock"-wish list) would be MUCH "easier" to use, I guess. ;-)
>

http://groups.google.com/groups?q=g:thl949715942d&dq=&hl=en&lr=&ie=UTF-8&
sel
m=3CBF30D9.12B98D8A%40web.de
>"bool unwinding< T >(T*)" (or macro/whatever)
>
>that would basically tell whether an object
>(identified via "this" or some other ptr) has
>entered destruction phase, and, if yes, whether
>it's due to 'stack-unwinding' on exception
>propagation?! Or am I just missing something
>here?

How would that be used?

Thanks for the advice.

hys

--
Hillel Y. Sims
FactSet Research Systems
hsims AT factset.com

Alexander Terekhov

unread,
Sep 10, 2002, 5:58:40 AM9/10/02
to

"Hillel Y. Sims" wrote:
>
> "Alexander Terekhov" <tere...@web.de> wrote in message
> news:3D75E040...@web.de...
> >
> > "Hillel Y. Sims" wrote:
> >
> > [...m_wasHandling(std::uncaught_exception())...]
> >
> > I like the idea (but you should really call terminate(); NOT unexpected()
> > that might throw something else... and THAT WILL escape your "guard" with
> > cancelation left disabled, BTW),
>
> It's kind of weird - an unexpected-handler won't work exactly the same
> under NOTHROW_REGION violation as with throw() violation. Attempting to
> throw something like bad_exception will not do the usual "translation"
> thing because "throw()" itself is not in effect, so it seems like it
> would instead behave just like a typical secondary exception emitted
> during stack-unwinding of primary phase exception: immediate call to
> terminate() ....

Yes, presuming that "primary phase exception" is NOT considered caught
inside unexpected() when you invoke it ``manually'' via some d-tor vs
ES->unexpected() (or catch(something)->unexpected()) like invocation.

> > Another problem is that it doesn't necessarily preclude unwinding way up
> > to the guard destructor...
>

> Hmm.. throw() doesn't necessarily seem to be any different here: ...

Yes. You might want to take a look at my response to Herb Sutter's
"morals" on using {current} exception specifications in this thread:

http://groups.google.com/groups?threadm=3d2be46c.31857046%40news.online.no
(Subject: Is internal catch-clause rethrow standard?)

> >"bool unwinding< T >(T*)" (or macro/whatever)
> >
> >that would basically tell whether an object
> >(identified via "this" or some other ptr) has
> >entered destruction phase, and, if yes, whether
> >it's due to 'stack-unwinding' on exception
> >propagation?! Or am I just missing something
> >here?
>
> How would that be used?

~NoThrowGuard() { if ( std::unwinding( this ) ) std::terminate(); }

regards,
alexander.

0 new messages