What's the shortest path in C++11 (or newer) to create an RAII wrapper without having to write a new class?
Asked Answered
D

4

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?

Dripps answered 12/12, 2018 at 9:35 Comment(14)
Not sure you need to allocate anything?Waldos
@MatthieuBrucher You need to allocate to ensure a non-zero pointer. A non-zero pointer (non-null pointer) is basically what triggers destruction.Dripps
What about writing a simple class once and give it a lambda as you want at construction and call that at the destructor? Similar like a lock guard.Foray
i guess you are not looking for boosts BOOST_SCOPE_EXIT, right?Contraposition
@πάνταῥεῖ I'm trying to avoid writing new classes. We can argue on why, but I wanna see if there's a way around it.Dripps
@user463035818 Never heard of it. I'm looking into the answer now. It has it.Dripps
@TheQuantumPhysicist I am not talking about new classes (<- plural), just a single one you can use for all of these cases. It would be very simple, and you even can make it a template if you need to have stuff forwarded to the lambda.Foray
are you sure that the deleter is only called for non-null pointers? I couldnt find anything on thatContraposition
@πάνταῥεῖ I know, buddy. I have one that I discussed before on Stackoverflow (I call SmartHandle). But, again, I don't have the freedom to do that in all the projects I work at depending on many circumstances, ranging from hastiness down to project organization problems.Dripps
@user463035818 From my understanding of smart pointers, they are destroyed with something like if(!ptr) delete ptr;. I don't see how else.Dripps
I really dont know but I would expect that this is what happens when you dont provide your own deleter, while a custom deleter is called alwaysContraposition
@user463035818 calling delete on nullptr is a noop. IRC, it's UB to do anything else if you overload it.Flintshire
@TheQuantumPhysicist What a pity if you can't do the necessary work because of such silly circumstances. But I know that kind of situation.Foray
@user463035818 while a custom deleter is called always Are you sure? Standard says otherwise: eel.is/c++draft/unique.ptr.single#dtor-2.Commemorative
K
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

Kimes answered 12/12, 2018 at 9:45 Comment(9)
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!Dripps
Interesting solution for the problem.Foray
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://mcmap.net/q/56847/-what-is-the-performance-overhead-of-std-function). "Classic" ScopeGuard, such as that from Boost, might be more efficient.Commemorative
@DanielLangr How about this solution? Is this super-perfect?Dripps
@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.Commemorative
@TheQuantumPhysicist Your solution is better. I have improved slightly by not relying on a local bool (easily copy pastable to different context). stackoverflow.com/a/53742232Holophrastic
@TheQuantumPhysicist Also note that there is a proposal for adding scope guards (std::scope_...) into the Stadndard library: open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0052r9.pdf.Commemorative
@DanielLangr I would appreciate it more if they add destructors to lamdas... that would be a killer feature!Dripps
Here's an arguably a more efficient variant (no std::function). ideone.com/NnU6zfFerryboat
M
7

You can use BOOST_SCOPE_EXIT

auto prevState{currState};
currState = newState;
BOOST_SCOPE_EXIT(&currState, &prevState)
{
     currState = prevState;
} BOOST_SCOPE_EXIT_END
Montano answered 12/12, 2018 at 9:41 Comment(1)
Yay. Boost. :-/Counterintelligence
K
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

Kimes answered 12/12, 2018 at 9:45 Comment(9)
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!Dripps
Interesting solution for the problem.Foray
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://mcmap.net/q/56847/-what-is-the-performance-overhead-of-std-function). "Classic" ScopeGuard, such as that from Boost, might be more efficient.Commemorative
@DanielLangr How about this solution? Is this super-perfect?Dripps
@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.Commemorative
@TheQuantumPhysicist Your solution is better. I have improved slightly by not relying on a local bool (easily copy pastable to different context). stackoverflow.com/a/53742232Holophrastic
@TheQuantumPhysicist Also note that there is a proposal for adding scope guards (std::scope_...) into the Stadndard library: open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0052r9.pdf.Commemorative
@DanielLangr I would appreciate it more if they add destructors to lamdas... that would be a killer feature!Dripps
Here's an arguably a more efficient variant (no std::function). ideone.com/NnU6zfFerryboat
H
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;
}
Holophrastic answered 12/12, 2018 at 11:38 Comment(0)
X
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)

Xenomorphic answered 15/12, 2018 at 11:9 Comment(1)
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.Dripps

© 2022 - 2024 — McMap. All rights reserved.