Skip to content

Instantly share code, notes, and snippets.

@Mankarse
Created April 14, 2012 07:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mankarse/2382599 to your computer and use it in GitHub Desktop.
Save Mankarse/2382599 to your computer and use it in GitHub Desktop.
#include "scope.hpp"
#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 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 {
test2();
}
catch(...) {
std::cout << "Exception caught\n";
}
}
#ifndef SCOPE_HPP
#define SCOPE_HPP
#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 && o) :
_cb()
{
_cb.swap(o._cb);
}
scope_cb &operator=(const scope_cb &) = delete;
scope_cb &operator=(scope_cb && o) {
if (this != &o) {
_cb = std::function<void()>();
_cb.swap(o._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)
{
return rollback_t([=]{ if(std::uncaught_exception()) cb(); });
}
#endif SCOPE_HPP
@Mankarse
Copy link
Author

Moving from a std::function does not guarantee that the std::function will be empty, only that the std::function will be in a "valid but unspecified state". The original code assumed that the moved-from std::function was empty, and so could spuriously perform cleanup.

@mpusz
Copy link

mpusz commented Apr 15, 2012

Thanks for pointing that out.
BTW: operator=() should return *this ;-)

@Mankarse
Copy link
Author

Thanks, fixed ;-)

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