do std::function and std::bind do dynamic memory allocation?

The standard doesn't specify, but in general it's easy to see that std::function must allocate memory at least in some cases:

struct huge { char c[10000]; };
void foo(const huge &);
std::function<void()>{std::bind(foo, huge{})};

On the other hand it's possible for it to avoid allocation in at least some cases by siting its function object inside a preallocated buffer inside the function object's footprint; obviously there is a tradeoff as this could make other uses take more stack memory. A good implementation would be able to avoid memory allocation when storing a raw function pointer in a function object, and possibly also for a mem_fn, but it's less likely that it'd do so for a bind.

For example, libstdc++ (g++) inlines (functor) object pointers, function pointers, and (non-virtual) member function pointers, as well as anything else that'd fit in the same footprint, e.g. stateless functors (union _Nocopy_types).

If you can, by inverting your control flow to accept templated functor objects instead of function you can avoid any extra memory allocation:

template<typename F>
void my_algorithm(const F &);
my_algorithm(std::bind(foo, huge{}));

I just did some research on this for the case of g++.

When it comes to std::function and dynamic memory allocation there are two key points.

  1. std::function can store objects of arbitrary size, this means it must perform dynamic memory allocation in some cases.
  2. there are certain types for which std::function is guaranteed not to throw exceptions. This implies that there are certain types it must store without dynamic memory allocation.

The implementation of std::function in gccs libstd+++ will store without dynamic memory allocation other things with size/alignment requirements less than or equal to the size/alignment requirements of the things it must store.

The largest thing it must store without dynamic memory allocation is a pointer to member function. On compilers based on the "itanium c++ ABI"* this is twice the size of a normal pointer. So you can store anything up to two pointers in size in a std::function in g++ without triggering dynamic memory allocation.

As far as I can tell std::bind just concatenates stuff together into an object, so binding anything to a member function will result in an object that is at least three pointers in size. Assigning this object to a std::function will result in dynamic memory allocation.

A better option is to use a lambda. This refers to the member function statically, giving you space to capture up to two pointers without triggering dynamic memory allocation.

To demonstrate I wrote some test code loosely based on yours. I got rid of the string and list and used a const char * (to avoid std::string related memory allocations) and placement new (this code was only intended to be built, not to be run) instead and fed it into godbolt.

#include <functional>
using namespace std;

class Thing  
{
    void foo();
    void bar();
    void function (const char * message);
};

char baz[1024];

void Thing::foo() {
    new (baz) std::function<void()>(std::bind(&Thing::function, this, "Hello"));
}


void Thing::bar() {
    const char * s = "Hello";
    new (baz) std::function<void()>([this,s](){function(s);});
}

The results were.

Thing::foo():
        mov     r3, #0
        push    {r4, r5, r6, lr}
        ldr     r4, .L34
        mov     r6, r0
        sub     sp, sp, #16
        mov     r0, #16
        str     r3, [r4, #8]
        bl      operator new(unsigned int)
        ldr     r2, .L34+4
        mov     r1, #0
        mov     r3, r0
        str     r2, [sp]
        mov     r2, sp
        ldr     r5, .L34+8
        ldr     lr, .L34+12
        ldr     ip, .L34+16
        str     r1, [sp, #4]
        str     r6, [r0, #12]
        str     r0, [r4]
        str     r5, [r3, #8]
        ldm     r2, {r0, r1}
        str     lr, [r4, #12]
        stm     r3, {r0, r1}
        str     ip, [r4, #8]
        add     sp, sp, #16
        pop     {r4, r5, r6, pc}
        ldr     r3, [r4, #8]
        cmp     r3, #0
        beq     .L27
        ldr     r1, .L34
        mov     r2, #3
        mov     r0, r1
        blx     r3
.L27:
        bl      __cxa_end_cleanup
.L34:
        .word   .LANCHOR1
        .word   Thing::function(char const*)
        .word   .LC0
        .word   std::_Function_handler<void (), std::_Bind<void (Thing::*(Thing*,     char const*))(char const*)> >::_M_invoke(std::_Any_data const&)
        .word   std::_Function_base::_Base_manager<std::_Bind<void (Thing::*(Thing*, char const*))(char const*)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
Thing::bar():
            ldr     r2, .L38
            sub     sp, sp, #8
            stm     sp, {r0, r2}
            add     r2, sp, #8
            ldr     r3, .L38+4
            ldmdb   r2, {r0, r1}
            ldr     ip, .L38+8
            ldr     r2, .L38+12
            stm     r3, {r0, r1}
            str     ip, [r3, #12]
            str     r2, [r3, #8]
            add     sp, sp, #8
            bx      lr
    .L38:
            .word   .LC0
            .word   .LANCHOR1
            .word   std::_Function_handler<void (), Thing::bar()::{lambda()#1}>::_M_invoke(std::_Any_data const&)
            .word   std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}> const&, std::_Manager_operation)

We can clearly see there is memory allocation in the bind case, but not in the lambda case.

* Which despite the name is used by g++ and clang++ across many different architectures.

Tags:

C++