Create a gist now

Instantly share code, notes, and snippets.

@mpusz /main.cpp Secret
Created Apr 13, 2012

What would you like to do?
D like scopes trial implementation
#include "scope.h"
#include <iostream>
class Action {
static unsigned _count;
unsigned _id;
public:
Action(bool valid):
_id(++_count)
{
std::cout << "Action " << _id << "\n";
if(!valid) {
std::cout << "Throwing exception from action " << _id << "!!!\n";
throw std::exception();
}
}
void Rollback()
{
std::cout << "Rollback " << _id << "\n";
}
void Cleanup()
{
std::cout << "Cleanup " << _id << "\n";
}
};
unsigned Action::_count = 0;
void test()
{
Action a1(true);
scope s1(at_exit( [&]{ a1.Cleanup(); }),
at_failure([&]{ a1.Rollback(); }));
{
Action a2(true);
scope s2(at_exit( [&]{ a2.Cleanup(); }),
at_failure([&]{ a2.Rollback(); }));
}
Action a3(true);
scope s3(at_failure([&]{ a3.Rollback(); }),
at_exit( [&]{ a3.Cleanup(); }));
Action a4(true);
scope s4(at_failure([&]{ a4.Rollback(); }));
Action a5(true);
scope s5(at_exit( [&]{ a5.Cleanup(); }));
Action a6(false);
scope s6(at_exit( [&]{ a6.Cleanup(); }),
at_failure([&]{ a6.Rollback(); }));
}
void test2()
{
Action a1(true);
scope s1(at_exit([&]{ a1.Cleanup(); }),
at_failure([&]
{
Action a3(true);
scope s3(at_exit([&]{ a3.Cleanup(); }),
at_failure([&]{ a3.Rollback(); }));
a1.Rollback();
}));
Action a2(false);
}
int main()
{
try {
test();
}
catch(...) {
std::cout << "Exception caught\n";
}
std::cout << "\n";
try {
test2();
}
catch(...) {
std::cout << "Exception caught\n";
}
}
#ifndef SCOPE_H
#define SCOPE_H
#include <functional>
#include <exception>
namespace details {
struct rollback_cb_t { };
struct cleanup_cb_t { };
template<typename T>
class scope_cb {
std::function<void()> _cb;
public:
scope_cb() = default;
scope_cb(std::function<void()> &&cb): _cb(std::move(cb)) {}
scope_cb(const scope_cb &) = delete;
scope_cb(scope_cb &&other)
{
std::swap(_cb, other._cb);
}
scope_cb &operator=(scope_cb other)
{
std::swap(_cb, other._cb);
return *this;
}
~scope_cb() { if(_cb) _cb(); }
};
}
typedef details::scope_cb<details::cleanup_cb_t> cleanup_t;
typedef details::scope_cb<details::rollback_cb_t> rollback_t;
class scope {
cleanup_t _cleanup;
rollback_t _rollback;
public:
scope(cleanup_t &&cleanup, rollback_t &&rollback = rollback_t()):
_cleanup(std::move(cleanup)), _rollback(std::move(rollback)) {}
scope(rollback_t &&rollback, cleanup_t &&cleanup = cleanup_t()):
_cleanup(std::move(cleanup)), _rollback(std::move(rollback)) {}
scope(const scope &) = delete;
scope(scope &&) = delete;
scope &operator=(const scope &) = delete;
scope &operator=(scope &&) = delete;
~scope() = default;
};
inline cleanup_t at_exit(std::function<void()> &&cb)
{
return cleanup_t(std::move(cb));
}
inline rollback_t at_failure(std::function<void()> &&cb)
{
// do not schedule anything if already in an exception handler
return std::uncaught_exception() ? rollback_t() : rollback_t([=]{ if(std::uncaught_exception()) cb(); });
}
#endif

This seems like a good idea but it is not. Consider:

void test2()
{
  Action a1(true);
  scope s1(
    at_exit([&]{ a1.Cleanup(); }),
    at_failure([&]{
      Action a3(true);
      scope s3(
        at_exit([&]{ a3.Cleanup(); }),
        at_failure([&]{ a3.Rollback(); }));
      a1.Rollback(); }));
  Action a2(false);
}

If the bugs in your library code are fixed (fixed version here), this function will print:

Action 1
Action 2
Throwing exception from action 2!!!
Action 3
Rollback 1
Rollback 3
Cleanup 3
Cleanup 1

Note that Action 3 is rolled back rather than cleaned up, even though its scope was not exited through an exception. This is not the behaviour of D's scope statements.

Basically, std::uncaught_exception() is useless for anything that you might want to use it for, because it does not take into account the possibility of nested exceptions. Read more at GotW #47.

Maybe a future version of C++ will make std::uncaught_exception() return the current exception depth or something, and this technique will become workable. In the meantime, you need to use explicit variables for signalling failure (as shown in the Boost.ScopeExit documentation).

Owner

mpusz commented Apr 15, 2012

@Mankarse: Thanks for interesting reading. I was familiar with Boost.ScopeExit and loki::ScopeGuard but I always wondered why they did not use std::uncaught_exception(). Now I know :-)
Thank you also for pointing std::function move related problem in my code. I modified the code a bit and added your test that now works as expected. However that sollution probably is still not exactly what D scopes can do?

I have implemented D's scope(failure) and scope(success) in C++, check https://github.com/panaseleus/stack_unwinding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment