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: 2012-10-10 19:57:43


Hello,

11.10.2012 2:31, Sergey Cheban wrote:
> 10.10.2012 23:47, Evgeny Panasyuk wrote:
>
>> I have just checked Boost.ScopeExit on-line manual
>> (http://www.boost.org/doc/libs/1_51_0/libs/scope_exit/doc/html/index.html).
>>
>> Approximately 14 of 16 BOOST_SCOPE_EXIT* samples in manual are in fact
>> emulations of SCOPE_FAILURE and SCOPE_SUCCESS.
>
> Yes, SCOPE_FAILURE seems to be useful. But could you please provide an
> use case for the SCOPE_SUCCESS? It seems to me that it is better to
> place SUCCESS code at the end of the scope.

1. Besides exceptions, there are different ways to create several ends
of scope. For instance, code block may has several returns, breaks,
continues.
So, there is not one end of scope - there are several. Placing SUCCESS
code manually to every end of scope is error prone (as well as
commit=true for ScopeGuards).
While code within BOOST_SCOPE_EXIT/SCOPE_SUCCESS/SCOPE_FAILURE executes
(or not, depending on exceptions state) at every end on scope.

Example:

{
     SCOPE_FAILURE(void) {
         log("could not update user info");
     } SCOPE_FAILURE_END

     File passwd("/etc/passwd");

     SCOPE_SUCCESS( (&passwd) ) {
         passwd.flush();
     } SCOPE_SUCCESS_END

     BOOST_SCOPE_EXIT( (&passwd) ) {
         passwd.close();
     } BOOST_SCOPE_EXIT_END

     // ... - may contain several returns, etc
}

2. SCOPE_SUCCESS can be scheduled to real end of scope, after all destructors which may throw, after all other SCOPE_SUCCESS scheduled after it which also may throw.
it is impossible to place manual actions somewhere between or after destructor calls, without use of artificial C++ code blocks:

void some_func()
{
     // Artificial code block
     {
         Something a,b,c;
         /* ... */
     }
     SUCCESS_ACTION;
}

Using SCOPE_SUCCESS it will become:

void some_func()
{
     SCOPE_SUCCESS(void) {
         SUCCESS_ACTION;
     } SCOPE_SUCCESS_END

     Something a,b,c;
     /* ... */
}

3. Andrei Alexandrescu mentioned D's scope(success) at his recent talk http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2012/Three-Unlikely-Successful-Features-of-D :
"[description of scope(failure) and scope(exit)]...
There is rarely used variant for completion, which is scope(success).
And then you celebrate, there is orchestra, fireworks if you wish.
It is not very often used, but people use it sometimes and it turns out that it defers completion"

> Note also that the exceptions cannot be thrown from the SCOPE_SUCCESS.

Why? Conversely it is pretty safe to throw exception from SCOPE_SUCCESS.
"passwd.flush();" above may safely throw on fail.

P.S. I think that having SCOPE_SUCCESS like feature "inside" objects
would be useful. For instance, from the 2. example above, there is "File
passwd".
We can use RAII and place BOOST_SCOPE_EXIT's passwd.close(); into File's
destructor:

{
     SCOPE_FAILURE(void) {
         log("could not update user info");
     } SCOPE_FAILURE_END

     File passwd("/etc/passwd");

     SCOPE_SUCCESS( (&passwd) ) {
         passwd.flush();
     } SCOPE_SUCCESS_END

     // passwd.~File() calls close()
}

But currently, we don't tool have tool for get rid of SCOPE_SUCCESS's passwd.flush(), i.e. deferred action. Current guideline is to call such actions manually.
I came up with notion of two-stage destructor which consist of two parts: deferred action (may throw) and release action(should not throw, never fail).
Where deferred action is executed only when object is destructed not due to stack unwinding - and such action may safely throw.
And release action is executed in any case.
https://github.com/panaseleus/stack_unwinding#two-stage-destructor
Current syntax is:

class RAII_Deferred
{
   bool fail_on_flush;
public:
   RAII_Deferred(bool fail_on_flush_) : fail_on_flush(fail_on_flush_)
   {
     cout << "acquiring resource" << endl;
   }
   TWO_STAGE_DESTRUCTOR_RELEASE(RAII_Deferred)
   {
     cout << "release resource" << endl;
   }
   // Deferred part of destructor. May fail(for instance fflush).
   TWO_STAGE_DESTRUCTOR_DEFERRED(RAII_Deferred)
   {
     cout << "flush pending actions on resource" << endl;
     if(fail_on_flush) throw 1;
   }
};

So, with such tool, we may have following File object:

{
     SCOPE_FAILURE(void) {
         log("could not update user info");
     } SCOPE_FAILURE_END

     File passwd("/etc/passwd");

     //passwd.~Deferred() callsflush(), when called not due to stack unwinding abd may safely throw

     // passwd.~File() calls close()
}

Best Regards,
Evgeny


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