15
\$\begingroup\$

Due to my needing to use C libraries inside C++, I kept finding myself wanting to be able to ensure that the free function got called, even if an exception got thrown. UTILS_SCOPE_EXIT ensures that the function will be called at the end of scope. External documentation shows how to use it, and also clarifies that it is not allowed to have multiple calls on the same line (because __COUNTER__ is not always supported):

utils.hpp

#pragma once
#include <functional>

namespace utils { namespace internal {

class ScopeExit_ {
public:
    ScopeExit_(std::function<void()> callback)
        : callback_{ callback }
    {}
    ~ScopeExit_()
    {
        callback_();
    }
private:
    std::function<void()> callback_;
};

#define UTILS_INTERNAL_CAT_HELPER(x, y) x ## y
#define UTILS_INTERNAL_CAT(x, y) UTILS_INTERNAL_CAT_HELPER(x, y)
#define UTILS_SCOPE_EXIT(...) utils::internal::ScopeExit_ UTILS_INTERNAL_CAT(utils_internal_ScopeExit_, __LINE__){ __VA_ARGS__ }

} }

This can be used like so:

#include <iostream>

int main()
{
    UTILS_SCOPE_EXIT([]{ std::cout << "1" << std::endl; });
    UTILS_SCOPE_EXIT([]{ std::cout << "2" << std::endl; });
    UTILS_SCOPE_EXIT([]{ std::cout << "3" << std::endl; });
    std::cout << "4" << std::endl;
    std::cout << "5" << std::endl;
}

Outputs:

4
5
3
2
1
\$\endgroup\$
4
  • 2
    \$\begingroup\$ An alternative (probably safer) is to use actual RAII by wrapping the pointers you have to free in a smart pointer with a custom deallocator, e.g. coliru.stacked-crooked.com/a/ffb8afb7b5b0b92f \$\endgroup\$
    – You
    Jul 8, 2016 at 10:57
  • 1
    \$\begingroup\$ Yes, use a unique_pointer when you can, with a custom deleter to involve free rather than delete. \$\endgroup\$
    – JDługosz
    Jul 8, 2016 at 13:24
  • \$\begingroup\$ For a general purpose scope exit for things that don't work with the common special classes, you should use a template to capture a lambda rather than std function. \$\endgroup\$
    – JDługosz
    Jul 8, 2016 at 13:25
  • \$\begingroup\$ This is exactly what std::unique is for. \$\endgroup\$ Jul 9, 2016 at 5:30

3 Answers 3

3
\$\begingroup\$

You can achieve a better effect using std::unique_ptr:

#include <memory>
#include <iostream>
#include <string.h>
#include <stdlib.h>

// using free1() to prove that the data is being freed.
void free1(void* data)
{
    std::cerr << "Freeing: " << reinterpret_cast<char*>(data) << "\n";
    free(data);
}

void SomeCFunction(char* x);

int main()
{
    // Hold C pointers in std::unique_ptr with an explicit deleter.
    std::unique_ptr<char, void(*)(void*)> holder{nullptr, free1};

    // Then, just reset with new values.
    holder.reset(strdup("Hi"));
    holder.reset(strdup("Bye"));

    SomeCFunction(holder.get());
}
\$\endgroup\$
4
  • \$\begingroup\$ Clarification on why I accepted this answer: I stopped using my UTILS_SCOPE_EXIT for pointer management in favor of std::unique_ptr and typedefs to avoid template bloat. All other cases where I found myself wanting UTILS_SCOPE_EXIT ended up being removed through refactoring. TLDR: I found that my desire for UTILS_SCOPE_EXIT was a code smell, and this answer is what helped me realize it. \$\endgroup\$
    – Justin
    Jul 13, 2016 at 21:51
  • \$\begingroup\$ Note that unique_ptr's destructor is not called if pointer holds null value \$\endgroup\$
    – yanpas
    Jan 15, 2019 at 12:05
  • \$\begingroup\$ @yanpas The destructor is always called for automatic objects. Do you mean free1() is not called if the pointer is null. \$\endgroup\$ Jan 15, 2019 at 18:50
  • \$\begingroup\$ @MartinYork exactly \$\endgroup\$
    – yanpas
    Jan 15, 2019 at 18:52
17
\$\begingroup\$

Efficiency

The main issue with std::function<void()> is that it is heavy in memory usage. It comes in at a minimum of 32 bytes in G++ and (if memory serves me right) 40 bytes in VC++2015 32-bit.

See: http://coliru.stacked-crooked.com/a/9503ca27f1faded8

Solution

Don't use std::function<void()>.


A lightweight implementation

I promise you that we can reduce the on-scope-exit object's size to a tiny 1 byte when all we want to do is take an action at the end of a scope by using lambdas and templates.

Note: The implementation requires the <utility> and <type_traits> headers.

Step 1: Make a helper function that creates a lambda that calls a callable object.

template <typename F, typename... Args>
auto make_lambda( F&& f, Args&&... f_args ) noexcept
{
    // important to capture by copy to prevent argument lifetime issues
    return [=] () mutable -> std::result_of_t<F( Args... )>
    {
        return std::forward<F>( f )( std::forward<Args>( args )... );
    };
}
  • This function creates a lambda that calls the argument callable. We do this so that we can store it for later use; for our use case, in a destructor so that it is called at the end of a scope.
  • As of C++14, the noexcept specifier is not part of a function's type, so when C++17 comes around and changes that, you'll probably want to come back to this function. See this for more information: https://stackoverflow.com/q/33589752/2296177

Step 2: A function to generate the object that calls the stored lambda in its destructor.

template<class F>
auto on_scope_exit( F&& f ) noexcept
{
    class unique_scope_exit_t final
    {
    private:
        using fn_t = decltype( make_lambda( std::move( f ) ) );

        fn_t fn_;

    public:
        ~unique_scope_exit_t()
            noexcept( noexcept( fn_() ) )
        {
            fn_(); // call it in our destructor
        }

        unique_scope_exit_t( F&& fn ) // can be extended for complex lambdas
            noexcept( std::is_nothrow_move_constructible<fn_t>::value )
            : fn_{ make_lambda( std::move( fn ) ) } // take ownership and store the lambda
        {}

        unique_scope_exit_t( unique_scope_exit_t&& rhs )
            noexcept( std::is_nothrow_move_constructible<fn_t>::value )
            : fn_{ std::move( rhs.fn_ ) }
        {}

        unique_scope_exit_t( unique_scope_exit_t const& ) = delete;
        unique_scope_exit_t& operator=( unique_scope_exit_t const& ) = delete;
        unique_scope_exit_t& operator=( unique_scope_exit_t&& ) = delete;
    };
    return unique_scope_exit_t{ std::move( f ) }; // we take ownership of argument 'f'
}
  • Having a class definition that is local to our function allows us to use the deduced type of the template type argument.
  • Take advantage of the <type_traits> header to make it optimal by declaring everything noexcept when possible.
  • You can provide a variadic template to the constructor in order to make full usage of make_lambda() and so that you can support complex end-of-scope actions. I leave this implementation to you, it is trivial.
  • The C++17 standard will guarantee copy elision for the function return.

As promised, one of our objects takes a single byte when its lambda doesn't capture anything. That is:

auto test = on_scope_exit( [] { std::cout << "exiting\n"; } );
static_assert( sizeof( decltype( test ) ) == 1, "!" );

This is as it implies... the size of our generated object only takes as many bytes as it needs.

We have achieved an implementation that is both flexible and efficient (in speed and size).

Sample usage

Similar to your usage, however there are no macros involved (personal preference). You can easily wrap this in a macro if it makes you happy.

#include <iostream>

int main()
{
    // remember to store the call's return to prevent it from running early
    auto exit1 = on_scope_exit( [] { std::cout << "1\n"; } );
    auto exit2 = on_scope_exit( [] { std::cout << "2\n"; } );
    auto exit3 = on_scope_exit( [] { std::cout << "3\n"; } );

    std::cout << "4\n";
    std::cout << "5\n";
}

Full demo: http://coliru.stacked-crooked.com/a/da1afa40ae073523


Due to comments.

Other possible implementations

As noted by Loki Astari and Nikita Kakuev, the previous implementation can increase the executable's size if it is used with many (very many) different types.

In a regular use case, I still think this is a better approach than constantly constructing and destroying 32+ bytes. For example, VC++2015's std::function<void()> is 40 bytes in 32-bit mode and 64 bytes in 64-bit mode.

As Nikita Kakuev points out here, make_lambda()'s signature might be over-generalized for this specific case, so it could be omitted in favour of a more succinct on_scope_exit().

The original thought was that you could make unique_scope_exit_t's constructor take a variadic template in order to allow complex end-of-scope actions by storing callables whose operator() have parameters.

If this isn't desired, we can simply remove make_lambda() and provide the following implementation of on_scope_exit() with a reduced executable size hit:

template<class F>
auto on_scope_exit( F&& f )
    noexcept( std::is_nothrow_move_constructible<F>::value )
{
    class unique_scope_exit_t final
    {
        F f_;

    public:
        ~unique_scope_exit_t()
            noexcept( noexcept( f_() ) )
        {
            f_();
        }

        explicit unique_scope_exit_t( F&& f )
            noexcept( std::is_nothrow_move_constructible<F>::value )
            : f_( std::move( f ) )
        {}

        unique_scope_exit_t( unique_scope_exit_t&& rhs )
            noexcept( std::is_nothrow_move_constructible<F>::value )
            : f_{ std::move( rhs.f_ ) }
        {}

        unique_scope_exit_t( unique_scope_exit_t const& ) = delete;
        unique_scope_exit_t& operator=( unique_scope_exit_t const& ) = delete;
        unique_scope_exit_t& operator=( unique_scope_exit_t&& ) = delete;
    };
    return unique_scope_exit_t{ std::move( f ) };
}

This provides semantics similar to a std::unique_ptr<> solution while making intent clear and keeping size down to what is actually used (std::unique_ptr<> does store a pointer of type T after all). Usage remains as before.

\$\endgroup\$
13
  • \$\begingroup\$ Oh! I didn't remember I could define classes inside a function. \$\endgroup\$
    – Justin
    Jul 8, 2016 at 6:11
  • 4
    \$\begingroup\$ (if memory serves me right) 40 bytes pun intended ? lol \$\endgroup\$ Jul 8, 2016 at 7:35
  • \$\begingroup\$ What is the point of the make_lambda function? Why doesn't it work without it? It seems to just construct a lambda which does what the passed in lambda does. \$\endgroup\$
    – Justin
    Jul 8, 2016 at 20:36
  • 1
    \$\begingroup\$ It comes in at a minimum of 32 bytes. That's not exactly very heavy in modern day systems. If you have billions around then maybe you would think about it more. Also if you use a templated class then each instantiation of the class will generate its own code. Since each lambda has its own unique type that is potentially one for every lambda. So the extra code you planted could well outway the cost of a std::function on some platforms. Note: Note generally disagreeing that I would prefer a template but I don't think that argument holds much weight. \$\endgroup\$ Jul 8, 2016 at 21:56
  • 2
    \$\begingroup\$ As for code: 1) Your make_lambda is supposed to bind arguments to provided callable. Yet, you never pass arguments to it. Your on_scope_exit doesn't take arguments as well. 2) You're capturing supposed arguments by copy in a non-mutable lambda (effectively adding const to copies) then std::forward'ing them using original const qualifiers. Uh-oh. Get rid of make_lambda, it's too broken and makes your code look scary. \$\endgroup\$ Jul 9, 2016 at 1:48
4
\$\begingroup\$

Create wrappers.

If you want to free something, you've probably created it (or performed some similar operation). UTILS_SCOPE_EXIT solves the problem, but does it solve the right one?

For example, let's say that you have some library XYZ that allows you to create textures like this:

XYZ_Texture x = XYZ_Create_Texture(...);
/* code... */
XYZ_Free_Texture(x);

UTILS_SCOPE_EXIT could help us solve the problem of freeing the texture if we encounter an error, but it doesn't really look nice. We're creating the wrong abstraction.

The wrapper:

class Texture
{
    XYZ_Texture x;

public:
    Texture(...) {x = XYZ_Create_Texture(...);}
    ~Texture() {XYZ_Free_Texture(x);}
    /* other code */
};

This way, you can not only go around the limitations of the library, it's also immediately obvious what's going on.

You could also use this approach to create environments - create an object that will call its destructor when scope ends or stack unwinds. The problem with this approach, however, is that if you want to pass some functions - like in your example - this doesn't really work that well(but should you?).

Use std::unique_ptr

The other option is to use std::unique_ptr. Its template looks like this:

template <class T, class Deleter> class unique_ptr<T[], Deleter>;

As you can see, there's a Deleter that you can pass as an argument to the template. Quoting cppreference:

The object is destroyed using a potentially user-supplied deleter by calling Deleter(ptr). The deleter calls the destructor of the object and dispenses the memory.

Therefore, if you think that creating a class is an overkill, you can create a nice pointer that will automatically call the free function once it's done.

These are the two approaches that I've used in the past, and they worked well.

\$\endgroup\$
4
  • \$\begingroup\$ This! Scope Exit is to accomplish one-off arbitrary actions: if you find yourself in need to copy/paste your Scope Exit line in multiple functions, you have a problem, if you find yourself using it to handle resources (and thus potentially leak them if you forget it), you have a problem. There's a reason the Scope Exit tool is rarely needed. \$\endgroup\$ Jul 8, 2016 at 11:50
  • 1
    \$\begingroup\$ The main problem with making a wrapper class is that I don't want to wrap the 10s of C objects, especially since you have to also write copy constructors, move constructors, assignment operators. But the unique_ptr could potentially be used with lambdas, which is a good idea. The only problem with the second approach is when we are managing something other than memory and aren't dealing with pointers \$\endgroup\$
    – Justin
    Jul 8, 2016 at 17:09
  • \$\begingroup\$ @Justin if the interface is the same, you could use templates and still go with wrappers. I can understand being lazy, but laziness is sometimes a bad idea. I still don't know your code though, so your approach might be better for this particular case. \$\endgroup\$ Jul 9, 2016 at 19:14
  • \$\begingroup\$ @MatthewRock Templates is a good idea, but you might have freeMyPointer1(), freeMyPointer2(), and similar functions that aren't quite named the same, so it doesn't work out so nicely. I guess I could take the function itself as a template parameter or a constructor argument... Hmm. \$\endgroup\$
    – Justin
    Jul 9, 2016 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.