Boost logo

Boost :

From: Alexander Terekhov (terekhov_at_[hidden])
Date: 2003-10-02 08:35:33


David Abrahams wrote:
>
> Alexander Terekhov <terekhov_at_[hidden]> writes:
>
> > Alan.Griffiths_at_[hidden] wrote:
> > [...]
>
> >> The problem is not the C++ standard; it is that is that catch (...)
> >
> > Don't get me started on this, please.
> >
>
> Really, don't. Alexander has repeatedly demonstrated his
> unwillingness or inability to explain and justify his claims in a way
> that makes sense to most people; asking him over and over to do so is
> just a waste of list bandwidth. I suggest you try to find someone
> else to ask who can be more helpful. For example, I sent an email to
> David Butenhof about this 2-phase EH business out of curiosity, since
> he seems to advocate it, but I have yet to hear back.

In the meantime, you might want to read this (forward-inline; I've
already posted the link to the entire thread):

David Butenhof wrote:
[...]
> >> > My main problem with try/finally is that try/finally is indeed a
> >> > "handler". I don't want cleanup stuff act like a "handler" in the
> >> > sense that I don't want it to cause unnecessary unwinding for the
> >> > unexpected exceptions. I'd have no problems with some "attached"
> >> > try/finally-like beast that wouldn't cause unnecessary unwinding
> >> > for the unexpected exceptions. In my book, unexpected exceptions
> >> > shall cause std::unexpected() invocation at throw point. Another
> >> > problem is that code in the finally scope is allowed to "steal"
> >> > the original exception via throwing another one; I hate that too.
> >>
> >> Once you've changed the program state (by running any form of cleanup),
> >> the program state presented to your std::unexpected() handler, or the
> >> contents of a final core dump, no longer represent the actual state of
> >> the program at the time the exception was raised. It's become inaccurate
> >> and potentially misleading. You may have the static call stack, but you
> >> have very likely lost critical information about how the program came to
> >> that point.
> >
> > Agreed. Perhaps we have a terminology problem. To me, 'unwinding'
> > IS 'cleanup'. I don't want to have unwinding/cleanup prior to the
> > std::unexpected() handler invocation. In two-phase EH, a search
> > for some matching catch-handler is nothing but a simple read-only
> > lookup, AFAIK.
>
> Ah. Perhaps it's not so much a terminology problem as a "scope" problem. I
> had read your "unexpected" catchall as "unhandled", probably mostly because
> I'm thinking in terms of general (language independent) exception
> infrastructure, whereas you may be talking about semantics peculiar to C++.

Yeah, let's try to forget the general (language independent)
"condition handling" with detached handlers and fully concentrate
on the C++ exception handling and unwinding, please.

The current C++ standard says:

"The process of calling destructors for automatic objects
 constructed on the path from a try block to a throw-expression
 is called 'stack unwinding.'"

That's okay (there's a nice definition of 'try-block' as well).

Now, the current C++ standard also has a couple of kinda-relevant
"implementation-defined" bits:

 a) "If no matching handler is found in a program, the function
     terminate() is called; whether or not the stack is unwound
     before this call to terminate() is implementation-defined",

 b) "In the situation where no matching handler is found, it is
     implementation-defined whether or not the stack is unwound
     before terminate() is called.".

and it also defines the semantics of exception specifications...
in effect, as just a bunch of noop/rethrowing function-try-block
catch-handlers with a trailing catch(...)-thing that invokes
std::unexpected().

I just hate that. To begin with, the definition of the term
'stack unwinding' makes me wonder what "try-block" is meant
here:

    int main() {
      object o; // <-- 'unwinding' is implementation-defined
      throw "trouble";
    }

    and here:

    void Main() throw() {
      object o; // <-- 'unwinding' is REQUIRED here
      throw "trouble";
    }

I believe that in both cases above, std::unexpected() shall
be invoked as the result of successful evaluating of throw-
expression and without any 'unwinding' taking place. Further,
I believe that ALL dtors shall have an implicit throw()-
nothing exception-spec imposed on them. Finally, I believe
that the standard should say that 'execution of each and
every thread' [initial/main including] shall be done "as if"

   whatever run(...whatever...) throw(std::thread_exit,
                                      std::thread_cancel);

is called; that implementation simply does finalization
of thread_exit or thread_cancel exception (if thrown)
resulting in thread termination and that any other uncaught
exception will end up in std::unexpected() invoked at throw
point.

>
> Yes, perhaps "pure C++" code should terminate "in place" when an exception
> cannot be finalized, without running any intervening destructors. This is
> indeed complicated by a 'catch()' clause that instead of finalizing
> actually resumes the unwind with a different exception. While I would agree
> that could be frustrating, it's also a deliberate action of the
> application, not something imposed by the model or environment, and I don't
> see it as a problem.

Agreed.

> Either it's there because it was perceived to be
> useful (whether you agree or not is beside the point), or it's an error
> that should be corrected in any case.

Well, <copy&paste>

I'll admit that I'll have no problems with something like

template<class _FwdIt, class _Tval> inline
void _Uninit_fill(_FwdIt _First, _FwdIt _Last, const _Tval& _Val,
        _Nonscalar_ptr_iterator_tag)
{ // copy _Val throughout raw [_First, _Last), arbitrary type
  _FwdIt _Next = _First;
  try {
    for (; _First != _Last; ++_First)
      _Construct(&*_First, _Val);
  }
  action_on_propagation_of(...) { /* THIS DOESN'T CATCH *UNEXPECTED* EXCEPTIONS */
                                   /* THIS DOES RETHROW EXCEPTIONS AUTOMATICALLY */
                                   /* NOTHING ELSE CAN BE THROWN FROM THIS SCOPE */
    for (; _Next != _First; ++_Next)
      _Destroy(&*_Next);
  }
}

</copy&paste>

You might want to take a look at this as well: (stuff like
"better finally for C++"/"cleanup { /*...*/ };", etc.)

http://groups.google.com/groups?threadm=3CBA86A3.9C6805AF%40web.de
(Subject: Re: Alexandrescu on error handling at ACCU conference 2002)

>
> However, many (perhaps even most) applications aren't "pure C++". If the
> call stack includes non-C++ exception-aware code, the situation becomes
> very different. An unwind analyzer might, in theory, determine that a
> destructor, or a POSIX cleanup handler, CANNOT finalize the exception, only
> change state, and could therefore continue unwinding to determine whether
> the application can actually finalize and continue -- or otherwise dump
> core without letting anything change the state. However, on most systems
> even C++ object destructors are likely implemented as local PC-range
> exception scopes, indistinguishable at runtime from an Ada TRY/FINALLY or a
> C __try/__except.

I'd love to declare all such systems "obsolete"... if not broken. ;-)

regards,
alexander.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk