Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple way to execute code at the end of function scope [duplicate]

Tags:

c++

In test code occasionally I want to set/mock some global variables and at the end of test/scope I want to restore these variables. For example:

BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
    std::string csrfSave = Http::getCsrfToken();
    ... some test code
    Http::setCsrfToken(csrfSave); // restore csrf
}

Obvious problem here is that if some test code returns (or throws) before reaching the end you won't restore that csrfSave variable. So, simple improvement is to write some simple struct wrapper that restores the value automatically in dtor:

struct CsrfSave
{
    std::string csrfSave;
    CsrfSave()
    {
        csrfSave = Http::getCsrfToken();
    }
    ~CsrfSave()
    {
        Http::setCsrfToken(csrfSave);
    }
};
BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
    CsrfSave csrfSave;
    ... some test code
    // dtor of CsrfSave restores csrf
}

This solves the problem in general, however, for each function you'd need to write lots of boilerplate code. So, the question is: what are shortest and simplest approaches could be used to achieve the same, while minimizing and possibly avoiding all that boilerplate.

One such approach that I use, and I'm not too happy about it:

BOOST_AUTO_TEST_CASE(HttpClientCsrf)
{
    std::string csrfSave = RBX::Http::getLastCsrfToken();
    shared_ptr<void> csrfSaveScope(NULL, [&](void*) {
        RBX::Http::setLastCsrfToken(csrfSave);
    });
    ... some test code
}

Is there anything better? I'd prefer to avoid writing any utility classes and would prefer to avoid boost (unless it's something that's included in next std). This kind of pattern happens quite often in different projects (that do not share code) and each time I end up writing either simple struct wrapper or generic wrapper that takes lambda and I would like to see other approaches that can be coded in-place just like I showed in my shared_ptr exmpale

like image 676
Pavel P Avatar asked Jan 02 '23 10:01

Pavel P


1 Answers

(Adding these comments in later. Apologies for just pasting my code. I'm doing 3 things at once here at work )

Obviously you want some object with a destructor that gets cleaned up at function scope.

A deferred call is a template that holds the lambda you specify. Since it can only take the given lambda by move, you have ownership semantics. The only thing left is to return one of these as an rvalue reference. That's the job of the defer() function. It takes in your lambda and returns it in a deferred call. You save that object in a local variable and then C++ takes care of the rest.

This was actually the thing that really helped me "get" move semantics in the first place. I admit this technique is not original

auto cleanup = defer([] { /* call me at end of scope */ );

Note that this technique is not original.

// =============================================================================
// deferred_call:
// --------------
// This struct enables us to implement deferred function calls simply in
// the defer() function below.  It forces a given function to automatically
// be called at the end of scope using move-only semantics.  Most
// commonly, the given function will be a lambda but that is not required.
// See the defer() function (below) for more on this
// =============================================================================
template <typename FUNC>
struct deferred_call
{
    // Disallow assignment and copy


    deferred_call(const deferred_call& that) = delete;
    deferred_call& operator=(const deferred_call& that) = delete;

    // Pass in a lambda

    deferred_call(FUNC&& f) 
        : m_func(std::forward<FUNC>(f)), m_bOwner(true) 
    {
    }

    // Move constructor, since we disallow the copy

    deferred_call(deferred_call&& that)
        : m_func(std::move(that.m_func)), m_bOwner(that.m_bOwner)
    {
        that.m_bOwner = false;
    }

    // Destructor forces deferred call to be executed

    ~deferred_call()
    {
        execute();
    }

    // Prevent the deferred call from ever being invoked

    bool cancel()
    {
        bool bWasOwner = m_bOwner;
        m_bOwner = false;
        return bWasOwner;
    }

    // Cause the deferred call to be invoked NOW

    bool execute()
    {
        const auto bWasOwner = m_bOwner;

        if (m_bOwner)
        {
            m_bOwner = false;
            m_func();
        }

        return bWasOwner;
    }

private:
    FUNC m_func;
    bool m_bOwner;
};


// -----------------------------------------------------------------------------
// defer:  Generic, deferred function calls
// ----------------------------------------
//      This function template the user the ability to easily set up any 
//      arbitrary  function to be called *automatically* at the end of 
//      the current scope, even if return is called or an exception is 
//      thrown.  This is sort of a fire-and-forget.  Saves you from having
//      to repeat the same code over and over or from having to add
//      exception blocks just to be sure that the given function is called.
//
//      If you wish, you may cancel the deferred call as well as force it
//      to be executed BEFORE the end of scope.
//
// Example:
//      void Foo()
//      {
//          auto callOnException  = defer([]{ SomeGlobalFunction(); });
//          auto callNoMatterWhat = defer([pObj](pObj->SomeMemberFunction(); });
//
//          // Do dangerous stuff that might throw an exception ...
//
//          ...
//          ... blah blah blah
//          ...
//
//          // Done with dangerous code.  We can now...
//          //      a) cancel either of the above calls (i.e. call cancel()) OR
//          //      b) force them to be executed (i.e. call execute()) OR
//          //      c) do nothing and they'll be executed at end of scope.
//
//          callOnException.cancel();    // no exception, prevent this from happening
//
//          // End of scope,  If we had not canceled or executed the two
//          // above objects, they'd both be executed now.
//      }
// -----------------------------------------------------------------------------

template <typename F>
deferred_call<F> defer(F&& f)
{
    return deferred_call<F>(std::forward<F>(f));
}
like image 181
Joe Avatar answered Jan 05 '23 01:01

Joe