Boost logo

Boost :

Subject: Re: [boost] [scope_exit] D-style scope(failure) and scope(success) in C++
From: Evgeny Panasyuk (evgeny.panasyuk_at_[hidden])
Date: 2013-09-29 21:35:57


30.09.2013 2:55, Emil Dotchevski:
>> 1. scope(failure/success) runs specified code block. Sometimes such code
>> block can be reused - in that case custom guard object is preferred over
>> copy-pasting code.
>
> That is still the scope failure use case, which presumably has to deal
> with exceptions in the failure branch.

What do you mean? Every use case of uncaught_exception_count has to deal
something with exceptions.
And yes, that use case is similar to scope(failure/success), but it
cannot be implemented on top of it. In order to implement it user need a
tool: uncaught_exception_count/unwinding_indicator. And I think that
tool is closer to Boost.Exception than to Boost.ScopeExit.

>> 2. Temporary objects:
>> log() << may_throw() << endl;
>> log() returns some temporary object which does some finalizing work in
>> destructor. If that destructor is called due to stack unwinding - then
>> finalizing part will be different.
>
> Here, if may_throw() throws, ~log() must not throw. Therefore it is
> possible for the user to not be notified about a logging failure. If
> it is critical for the user to be notified about all logging failures,
> then then ~log() must not do any logging. How critical this
> notification is doesn't depend on whether or not may_throw() emits an
> exception.

It is not only about logging.
Temporary object may do some job related to invariants. For instance it
can be used to get "strong guarantee" in some cases:
     may_throw( x.temp_inner_invariant() );
destructor of object returned by temp_inner_invariant can restore
original state on exception from may_throw.
Or it can be opposite - do some invariant keeping job on success and do
nothing on failure (or maybe replace inner data with cheap default to
get something like "basic guarantee").

Concrete example: suppose we have class UnsafeString:
class UnsafeString
{
     vector<char> data;
     size_t length;
...
invariant is length == strlen(&data[0]);
Such string can be filled by API which accepts only char* in following way:
     UnsafeString s;
     unsafe_api( s.get_buffer(1024) );
get_buffer returns auxiliary wrapper which implicitly converts to char*
and in destructor maintains invariant of UnsafeString.
If unsafe_api would throw exception - then auxiliary wrapper can use
unwinding_indicator to spot it and select appropriate approach (restore
to original or restore to cheap default value).

>> 3. Stack objects which do work in destructor that may fail.
>> Traditional example is File object which requires flush(may fail) and
>> release handle at the end of lifetime.
>> Typical solution is to place .flush() manually and release handle in
>> destructor:
>> {
>> File a,b;
>> // ...
>> b.flush(); // may throw
>> a.flush(); // may throw
>> }
>
> The motivation here is to support throwing destructors. This becomes a
> self-fulfilling prophecy: you write destructors that may throw, which
> forces everyone else to deal with that.

I am talking about throwing only from destructors of stack objects.
There are many reasons to not throw from objects which are part of
others, for instance from element of std::vector or from element of
plain array. I do not offer to throw from every kind of destructor.
But it is OK to throw from desctructor of "stack object" as long as you
do that conditionally based on unwinding indicator.
That can be controversial - but it is just one of examples of unwinding
indicator usage. It is up to developer to decide whenever use it or not.

> It is incorrect for objects that are no longer needed to be able to fail to go away, therefore
> destructors must not throw.

Why? Semantically it is exactly same to placing flush by hands.

-- 
Evgeny Panasyuk

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