std::function with static allocation in c++

Viewed 2266

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

4 Answers

My attempt to run the solution given Here, encountered with some issues. After fixing them, seems to work fine.

Will be happy for any review as I am not a c++ expert!

Issues and fixes:

  • error: lambda expression in an unevaluated operand.

removed the decltype. ( was not present in original code so I guess its safe(???)

using aligned_t = detail::aligned_union<0,
                                        CallableT<void(*)()>, 
                                        //CallableT<decltype([]()->void {})>,
                                        CallableT<void (CallableT<void(*)()>::*)()>
                                >;

  • Under C++11, errors in code block:

error: fields must have a constant size: 'variable length array in structure' extension will never be supported

error: 'aligned' attribute requires integer constant

error: constexpr variable 'alignment_value' must be initialized by a constant expression

(Note: this code is replacing std::aligned_union)

namespace detail { 
    template <size_t Len, class... Types>
    struct aligned_union {
        static constexpr size_t alignment_value = std::max({alignof(Types)...}); // ERROR HERE C++11
        struct type {
            alignas(alignment_value) char _s[std::max({Len, sizeof(Types)...})];  // ERROR HERE C++11
        };
    };
}

Used 'external' help from ETLCPP - which has support for embedded, file: largest.h. Error block was replaced with :

#include"etl/largest.h"
template<typename ...Types>
using largest_t = typename etl::largest_type<Types...>::type;

namespace detail {
template <size_t Len, class... Types>
struct aligned_union {
    static constexpr size_t alignment_value = etl::largest_alignment<Types...>::value; //std::max({alignof(Types)...});
    struct type {
        alignas(alignment_value) char _s[sizeof(largest_t<Types...>)]; //[std::max({Len, sizeof(Types)...})];
    };
};
}

Looked redundant, removed:

//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(*)()>::*)()>)
    //        )
    //    );
    //};

  • replaced std::forward with etl::forward file: utility.h

  • Had anew ,and delete errors : Undefined symbol operator delete (void)*

So added ( I never allocate.. ):

  // Define placement new if no new header is available
  inline void* operator new(size_t, void* p) { return p; }
  inline void* operator new[](size_t, void* p) { return p; }

  inline void operator delete(void*, void*) {}
  inline void operator delete[](void*, void*) {}
  inline void operator delete[](void*) {}

Still getting a warning thought (???):

: warning: replacement function 'operator delete' cannot be declared 'inline' [-Winline-new-delete]
    inline void operator delete(void* )  {}

  • Linker error: Error: L6218E: Undefined symbol __cxa_pure_virtual ).

Probably because of virtual distractor : (ref)

 virtual ~ICallable() = default;

Had to add this : ( any other solution ???)

extern "C" void __cxa_pure_virtual() { while (1); }
Related