Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

If I do this :-

class Thing  
{
    ...
    void function (const std::string& message);
};

std::list<std::function<void()>> work;

and in some member of "Thing"

work.push_back(std::bind(&Thing::function, this, "Hello"));

Does either the call to std::bind or the use of std::function<> cause any dynamic memory allocation using new or otherwise? Or is all the storage allocated at compile time? If the standard doesn't say anything, what about in visual studio 2012 as my program will only need to build on there, and for efficiency I probably need to avoid dynamic memory allocations in the place where I am thinking of using this mechanism.

like image 494
jcoder Avatar asked Nov 09 '12 11:11

jcoder


People also ask

Which function is used to dynamically allocate the memory?

C malloc() method The “malloc” or “memory allocation” method in C is used to dynamically allocate a single large block of memory with the specified size. It returns a pointer of type void which can be cast into a pointer of any form.

Is std :: function allocating?

As it happens, std::function is guaranteed to not allocate if constructed from a function pointer, which is also one word in size. So constructing a std::function from this kind of lambda, which only needs to capture a pointer to an object and should also be one word, should in practice never allocate.

Which C++ operator is used to allocate memory dynamically?

To dynamically allocate memory in C++, we use the new operator. De-allocation: Deallocation is the "clean-up" of space being used for variables or other data storage.

Is std :: string dynamically allocated?

Inside every std::string is a dynamically allocated array of char .


2 Answers

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{}));
like image 87
ecatmur Avatar answered Sep 18 '22 13:09

ecatmur


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.

like image 35
plugwash Avatar answered Sep 19 '22 13:09

plugwash