11

Often I'm in a situation where I need a simple RAII wrapper, but I wouldn't want to create a whole new class for this for many reasons including time constraints and organization problems. My quick-n-dirty solution is the following.

Say I want to make sure that by the end of the scope, I want a boolean to switch back to its original state:

bool prevState = currState;
currState      = newState;
std::unique_ptr<int, std::function<void(int*)>> txEnder(new int(0), [&prevState](int* p) {
    currState = prevState;
    delete p;
});

This solution works fine, but the dirty part is the necessity to allocate and deallocate that integer just to make unique_ptr work and call the custom destructor at destruction.

Is there a cleaner way to do this without having to write a whole class, and get rid of the new for the dummy int?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189

4 Answers4

7

You can use BOOST_SCOPE_EXIT

auto prevState{currState};
currState = newState;
BOOST_SCOPE_EXIT(&currState, &prevState)
{
     currState = prevState;
} BOOST_SCOPE_EXIT_END
user7860670
  • 35,849
  • 4
  • 58
  • 84
6

A little bit better than yours: You can use &prevState in the custom destructor without deleting it, so you do not need to new and delete something:

void foo(bool & currState, bool newState)
{
    bool prevState = currState;
    currState      = newState;
    std::unique_ptr<bool, std::function<void(bool*)>> txEnder(&prevState, [&prevState, &currState](bool* p) {
        currState = prevState;
    });
    cout << "currState: " << currState << endl;
}

You also forgot to capture currState in the lambda.

Here is an example: https://ideone.com/DH7vZu

mch
  • 9,424
  • 2
  • 28
  • 42
  • This is perfect. I don't know how this didn't occur to me. I'm just being a little paranoid thinking whether this breaks any rules! – The Quantum Physicist Dec 12 '18 at 09:53
  • Interesting solution for the problem. – πάντα ῥεῖ Dec 12 '18 at 09:59
  • 1
    I would just note that this solution might not be the most efficient one, since `std::function` applies type erasure, which implies dynamic memory allocations (possibly small-buffer optimized) and virtual function overhead (see, e.g., https://stackoverflow.com/a/9088690/580083). "Classic" ScopeGuard, such as that from Boost, might be more efficient. – Daniel Langr Dec 12 '18 at 10:11
  • @DanielLangr How about [this solution](https://ideone.com/wFMT8N)? Is this super-perfect? – The Quantum Physicist Dec 12 '18 at 10:29
  • @TheQuantumPhysicist It's better IMO, but I wouldn't call it super-perfect. Unique pointers are unique pointers and scope guards are scope guards. Both can be used in multiple scenarios, but primarily, for the functionality you want are the latter ones. Unfortunately, they are not part of the Standard so we have to make trade-offs. – Daniel Langr Dec 12 '18 at 11:38
  • @TheQuantumPhysicist Your solution is better. I have improved slightly by not relying on a local bool (easily copy pastable to different context). https://stackoverflow.com/a/53742232/ – balki Dec 12 '18 at 11:41
  • 1
    @TheQuantumPhysicist Also note that there is a proposal for adding scope guards (`std::scope_...`) into the Stadndard library: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0052r9.pdf. – Daniel Langr Dec 12 '18 at 11:57
  • 1
    @DanielLangr I would appreciate it more if they add destructors to lamdas... that would be a killer feature! – The Quantum Physicist Dec 12 '18 at 12:12
  • Here's an arguably a more efficient variant (no std::function). https://ideone.com/NnU6zf – n. m. could be an AI Dec 12 '18 at 15:56
0

Don't use std::function. It creates a lot of code including vtables. https://gcc.godbolt.org/z/XgDoHz
If you absolutely don't want to use any external class or function, do below:

bool foo_2() {
    bool f = false;
    auto eos = [&](void*){
        f = true;
    };
    std::unique_ptr<void, decltype(eos)> h{&eos,std::move(eos)};
    return f;
}

If you are ok with a little reusable function, below works. This abstracts the unused void*.

C++14 or later

template<class F>
auto call_at_end_of_scope(F&& f){
    auto eos = [f{std::forward<F>(f)}](void*){f();};
    return std::unique_ptr<void, decltype(eos)>{&eos,std::move(eos)};
}

bool foo_3() {
    bool f = false;
    auto handle = call_at_end_of_scope([&](){
        f = true;
    });
    return f;
}
balki
  • 26,394
  • 30
  • 105
  • 151
0

How about gsl::finally? Library is not so heavy as boost and finally does not use std::function, so can be easly inlined. Also no dynamic allocation of std::unique_ptr

using namespace std;

void foo(bool & currState, bool newState)
{
    auto revertState = gsl::finally([prevState = currState, &currState]{
        currState = prevState;
    });
    currState = newState;       
    cout << "currState: " << currState << endl;
}


int main() {
    bool state = false;
    foo(state, true);
    cout << "state: " << state << endl;
    return 0;
}

Online example: https://ideone.com/Xi1izz (with copied gsl::finally, since #include <gsl/gsl> is not available here)

R2RT
  • 2,061
  • 15
  • 25
  • Your solution may be correct, but the challenge was to have the shortest path. The answer that is accepted is super-short and doesn't have any dynamic allocation. – The Quantum Physicist Dec 15 '18 at 11:46