Std :: function with static allocation in C ++

I work in a memory-limited embedded environment where malloc / free new / delete is not recommended, and I'm trying to use the std :: function pattern to register callbacks. I do not have access to any STL methods in my target code, so I got into a bad situation when I had to replicate some of the STL functions. Function pointers are not an option for me because of the need to capture callers.

For example, I want to declare a class mailbox where you can register the onChange event

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

Thus, callers can register a lambda handler onChangethat can capture some variables that are relevant to the processing of the event.

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

I managed to find a great implementation std::function that seems to be exceptionally low and has what I need except that it includes dynamic memory.

If you look at the following code, dynamic memory is used in exactly one place, and it looks completely tied to the template, suggesting that its size should be known at compile time.

Can someone help me figure out how to reorganize this implementation so that it is completely static and removes the use of new / malloc? I am having trouble understanding why the CallableT size will not be computed at compile time.

( ). , make_unique/unique_ptr, *, .

#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();
}

: STL

+4
3

:

template <class A> class naive_function;
template <typename ReturnValue, typename... Args>
class naive_function<ReturnValue(Args...)> {
public:
    naive_function() { }
    template <typename T>
    naive_function(T t) : set_(true) {
        assert(sizeof(CallableT<T>) <= sizeof(callable_));
        new (_get()) CallableT<T>(t);
    }
    template <typename T>
    naive_function(T *ptr, ReturnValue(T::*t)(Args...)) : set_(true) {
        assert(sizeof(CallableT<T>) <= sizeof(callable_));
        new (_get()) CallableT<T>(ptr, t);
    }
    naive_function(const naive_function &c) : set_(c.set_) {
        if (c.set_) c._get()->Copy(&callable_);
    }
    ~naive_function() { 
        if (set_) _get()->~ICallable();
    }

    naive_function &operator = (const naive_function &c) {
        if (this != &c) {
            if (set_) _get()->~ICallable();
            if (c.set_) {
                set_ = true;
                c._get()->Copy(&callable_);
            }
            else
                set_ = false;
        }
        return *this;
    }
    ReturnValue operator()(Args... args) const {
        return _get()->Invoke(args...);
    }
    ReturnValue operator()(Args... args) {
        return _get()->Invoke(args...);
    }

private:
    class ICallable {
    public:
        virtual ~ICallable() = default;
        virtual ReturnValue Invoke(Args...) = 0;
        virtual void Copy(void *dst) const = 0;
    };
    ICallable *_get() { 
        return ((ICallable*)&callable_); 
    }
    const ICallable *_get() const { return ((const ICallable*)&callable_); }
    template <typename T>
    class CallableT : public ICallable {
    public:
        CallableT(const T& t)
            : t_(t) {
        }

        ~CallableT() override = default;

        ReturnValue Invoke(Args... args) override {
            return t_(std::forward<ARGS>(args)...);
        }
        void Copy(void *dst) const override {
            new (dst) CallableT(*this);
        }
    private:
        T t_;
    };
    template <typename T>
    class CallableT<ReturnValue(T::*)(Args...)> : public ICallable {
    public:
        CallableT(T *ptr, ReturnValue(T::*)(Args...))
            : ptr_(ptr), t_(t) {
        }

        ~CallableT() override = default;

        ReturnValue Invoke(Args... args) override {
            return (ptr_->*t_)(std::forward<ARGS>(args)...);
        }
        void Copy(void *dst) const override {
            new (dst) CallableT(*this);
        }
    private:
        T *ptr_;
        ReturnValue(T::*t_)(Args...);
    };

    static constexpr size_t size() {
        auto f = []()->void {};
        return std::max(
            sizeof(CallableT<void(*)()>),
            std::max(
                sizeof(CallableT<decltype(f)>),
                sizeof(CallableT<void (CallableT<void(*)()>::*)()>)
            )
        );
    };
    typedef unsigned char callable_array[size()];
    typename std::aligned_union<0, callable_array, CallableT<void(*)()>, CallableT<void (CallableT<void(*)()>::*)()>>::type callable_;

    bool set_ = false;
};

, .

, , unsigned char [] - max CallableT , - -. - , , . Lambda , - isnt , . _ CallableT. , , _ ICallable. .

, naive_function, T .

: ( , ) + / .

+1

, , . , , . , , , .

: ( ) .

3

  • . (std boost) . , , std::function.
  • . - . , , (, static_assert). , . -, , . . , function . , @radosław-cybulski .
  • . , . , , . , . std::function, , , , , , .

3, ( )

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)...);
    }
};

, , https://godbolt.org/g/6mKoca

+1

Source: https://habr.com/ru/post/1681387/


All Articles