Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::function with static allocation in c++

I am working in a memory constrained embedded environment where malloc/free new/delete are not advisable, and I'm trying to use the std::function pattern to register callbacks. I do not have access to any of the STL methods in my target code so I'm in the unfortunate situation of having to replicate some of the STL functionality myself. Function pointers are not an option for me due to the necessity for callers to have captures.

For instance, I wish to declare a class Mailbox where an onChange event can be registered

class Mailbox {
    std::function<void(int,int)> onChange;
};

That way, callers can register a lambda onChange handler that could capture this or other variables that matter for handling the event.

Since this is part of an API, I want to give the users of Mailbox maximim flexibility to either provide a function pointer, a lambda or a functor.

I have managed to find a great implementation of a std::function that appears to be exceptionally low-overhead and has exactly what I need except that it involves dynamic memory.

If you look at the following code, dynamic memory is used in exactly one place, and it appears fully scoped to the object being templated, suggesting to me that its size ought to be known at compile-time.

Can anyone help me understand how to refactor this implementation so that it is fully static and removes the use of new/malloc? I'm having trouble understanding why the size of CallableT wouldn't be calculable at compile-time.

Code below (not for the faint of heart). Note, it uses make_unique / unique_ptr but those can easily be substituted with new and * and I have tested that use case successfully.

#include <iostream>
#include <memory>
#include <cassert>
using namespace std;

template <typename T>
class naive_function;

template <typename ReturnValue, typename... Args>
class naive_function<ReturnValue(Args...)> {
public:
    template <typename T>
    naive_function& operator=(T t) {
        callable_ = std::make_unique<CallableT<T>>(t);
        return *this;
    }

    ReturnValue operator()(Args... args) const {
        assert(callable_);
        return callable_->Invoke(args...);
    }

private:
    class ICallable {
    public:
        virtual ~ICallable() = default;
        virtual ReturnValue Invoke(Args...) = 0;
    };

    template <typename T>
    class CallableT : public ICallable {
    public:
        CallableT(const T& t)
            : t_(t) {
        }

        ~CallableT() override = default;

        ReturnValue Invoke(Args... args) override {
            return t_(args...);
        }

    private:
        T t_;
    };

    std::unique_ptr<ICallable> callable_;
};

void func() {
    cout << "func" << endl;
}

struct functor {
    void operator()() {
        cout << "functor" << endl;
    }
};

int main() {
    naive_function<void()> f;
    f = func;
    f();
    f = functor();
    f();
    f = []() { cout << "lambda" << endl; };
    f();
}

Edit: added clarification on STL

like image 474
Jeremy Gilbert Avatar asked Jul 13 '17 18:07

Jeremy Gilbert


2 Answers

The name for what you're looking for is "in-place function". At least one very good implementation exists today:

  • sg14::inplace_function<R(A...), Size, Align>

There is also tj::inplace_any<Size, Align>, if you need/want the semantics of any.

like image 100
Quuxplusone Avatar answered Oct 21 '22 19:10

Quuxplusone


Let me preface this answer by saying that storing a general callable faces an interesting choice in terms of memory management. Yes, we can deduce the size of any callable at compile time but we can not store any callable into the same object without memory management. That's because our own object needs to have size independently of the callables its supposed to store but those can be arbitrarily big.

To put this reasoning into one sentence: The layout of our class (and its interface) needs to be compiled without knowledge about all of the callers.

This leaves us with essentially 3 choices

  1. We embrace memory management. We dynamically copy the callable and properly manage that memory through means of unique pointer (std or boost), or through custom calls to new and delete. This is what the original code you found does and is also done by std::function.
  2. We only allow certain callables. We create some custom storage inside our object to hold some forms of callables. This storage has a pre-determined size and we reject any callable given that can not adhere to this requirement (e.g. by a static_assert). Note that this does not necessarily restrict the set of possible callers. Instead, any user of the interface could set up a proxy-class holding merely a pointer but forwarding the call operator. We could even offer such a proxy class ourselves as part of the library. But this does nothing more than shifting the point of allocation from inside the function implementation to outside. It's still worth a try, and @radosław-cybulski comes closest to this in his answer.
  3. We don't do memory management. We could design our interface in a way that it deliberately refuses to take ownership of the callable given to it. This way, we don't need to to memory management and this part is completely up to our caller. This is what I will give code for below. It is not a drop-in replacement for std::function but the only way I see to have a generic, allocation-free, copiable type for the purpose you inteded it.

And here is the code for possibility 3, completely without allocation and fully self-contained (does not need any library import)

template<typename>
class FunctionReference;

namespace detail {
    template<typename T>
    static T&  forward(T& t)  { return t; }
    template<typename T>
    static T&& forward(T&& t) { return static_cast<T&&>(t); }

    template<typename C, typename R, typename... Args>
    constexpr auto get_call(R (C::* o)(Args...)) // We take the argument for sfinae
    -> typename FunctionReference<R(Args...)>::ptr_t {
        return [](void* t, Args... args) { return (static_cast<C*>(t)->operator())(forward<Args>(args)...); };
    }

    template<typename C, typename R, typename... Args>
    constexpr auto get_call(R (C::* o)(Args...) const) // We take the argument for sfinae
    -> typename FunctionReference<R(Args...)>::ptr_t {
        return [](void* t, Args... args) { return (static_cast<const C*>(t)->operator())(forward<Args>(args)...); };
    }

    template<typename R, typename... Args>
    constexpr auto expand_call(R (*)(Args...))
    -> typename FunctionReference<R(Args...)>::ptr_t {
        return [](void* t, Args... args) { return (static_cast<R (*)(Args...)>(t))(forward<Args>(args)...); };
    }
}

template<typename R, typename... Args>
class FunctionReference<R(Args...)> {
public:
    using signature_t = R(Args...);
    using ptr_t       = R(*)(void*, Args...);
private:
    void* self;
    ptr_t function;
public:

    template<typename C>
    FunctionReference(C* c) : // Pointer to embrace that we do not manage this object
    self(c),
    function(detail::get_call(&C::operator()))
    { }

    using rawfn_ptr_t = R (*)(Args...);
    FunctionReference(rawfn_ptr_t fnptr) : 
    self(fnptr),
    function(detail::expand_call(fnptr))
    { }

    R operator()(Args... args) {
        return function(self, detail::forward<Args>(args)...);
    }
};

For seeing how this then works in action, go to https://godbolt.org/g/6mKoca

like image 26
HeroicKatora Avatar answered Oct 21 '22 19:10

HeroicKatora