Skip to content

Instantly share code, notes, and snippets.

@mpusz

mpusz/main.cpp Secret

Created April 13, 2012 11:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpusz/dab4bec98a0baa2ebabe to your computer and use it in GitHub Desktop.
Save mpusz/dab4bec98a0baa2ebabe to your computer and use it in GitHub Desktop.
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
@Mankarse
Copy link

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).

@mpusz
Copy link
Author

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?

@evgeny-panasyuk
Copy link

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