Why can't we trivially copy std::function

3.2k views Asked by At

The reason for me to ask this is I need to store std::function in a vector, and the in-house vector we have in company basically is doing realloc if it needs more memory. (Basically just memcpy, no copy/move operator involves)

This means all the element we can put in our container need to be trivially-copyable.

Here is some code to demonstrate the problematic copy I had:

void* func1Buffer = malloc(sizeof(std::function<void(int)>));
std::function<void(int)>* func1p = new (func1Buffer) std::function<void(int)>();
std::function<void(int)>* func2p = nullptr;
*func1p = [](int) {};
char func2Buffer[sizeof(*func1p)];
memcpy(&func2Buffer, func1p, sizeof(*func1p));
func2p = (std::function<void(int)>*)(func2Buffer);
// func2p is still valid here
(*func2p)(10);
free(func1Buffer);
// func2p is now invalid, even without std::function<void(int)> desctructor get triggered
(*func2p)(10);

I understand we should support copy/move of the element in order to store std::function safely. But I am still very curious about what is the direct cause of invalid std::function copy above.

----------------------------------------------------UpdateLine----------------------------------------------------

Updated the code sample.

I have found the direct reason for this failure, by debugging our in-house vector more.

The trivially copied std::function has some dependency on original object memory, delete the original memory will trash the badly copied std::function even without the destruction of the original object.

Thanks for everyone's answer to this post. It's all valuable input. :)

4

There are 4 answers

0
Barry On BEST ANSWER

The problem is how std::function has to be implemented: it has to manage the lifetime of whatever object it's holding onto. So when you write:

{
    std::function<Sig> f = X{};
} 

we must invoke the destructor of X when f goes out of scope. Moreover, std::function will [potentially] allocate memory to hold that X so the destructor of f must also [potentially] free that memory.

Now consider what happens when we try to do:

char buffer[100000]; // something big
{
    std::function<void()> f = X{};
    memcpy(buffer, &f, sizeof(f));
}
(*reinterpret_cast<std::function<void()>*>(buffer))();

At the point we're calling the function "stored" at buffer, the X object has already been destroyed and the memory holding it has been [potentially] freed. Regardless of whether X were TriviallyCopyable, we don't have an X anymore. We have the artist formerly known as an X.

Because it's incumbent upon std::function to manage its own objects, it cannot be TriviallyCopyable even if we added the requirement that all callables it managed were TriviallyCopyable.


To work in your realloc_vector, you need either need something like function_ref (or std::function<>*) (that is, a type that simply doesn't own any resources), or you need to implement your own version of function that (a) keeps its own storage as a member to avoid allocating memory and (b) is only constructible with TriviallyCopyable callables so that it itself becomes trivially copyable. Whichever solution is better depends on the what your program is actually doing.

1
Vittorio Romeo On

But I am still very curious about what is the direct cause of invalid std::function copy above.

std::function cannot be TriviallyCopyable (or conditionally TriviallyCopyable) because as a generic callable object wrapper it cannot assume that the stored callable is TriviallyCopyable.

Consider implementing your own version of std::function that only supports TriviallyCopyable callable objects (using a fixed buffer for storage), or use a vector of function pointers if applicable in your situation.

1
Viktor Sehr On

A std::function might allocate memory for captured variables. As with any other class which allocates memory, it's not trivially copyable.

0
skypjack On

To be trivially copyable is something that is inherently related to a given type, not to an object.
Consider the following example:

#include<type_traits>
#include<functional>

int main() {
    auto l = [](){};
    static_assert(not std::is_trivially_copyable<decltype(l)>::value, "!");

    std::function<void(void)> f;
    bool copyable = std::is_trivially_copyable<decltype(f)>::value;
    f = l;

    // do something based on the
    // fact that f is trivially copyable
}

How could you enforce the property once you have assigned to the function the lambda, that is not trivially copyable?

What you are looking for would be a runtime machinery that gets a decision based on the actual object assigned to the function.
This is not how std::is_trivially_copyable works.
Therefore the compiler has to make a decision at compile-time regarding the given specialization for the std::function. For it's a generic container for callable objects and you can assign it trivially copyable objects as well as objects that aren't trivially copyable, the rest goes without saying.