std :: function在c ++中使用静态分配

Jer*_*ert 7 c++ lambda type-erasure

我在一个内存受限的嵌入式环境中工作,其中malloc/free new/delete是不可取的,我正在尝试使用std :: function模式来注册回调.我无法访问目标代码中的任何STL方法,因此我不得不自己复制一些STL功能.函数指针对我来说不是一个选项,因为调用者必须有捕获.

例如,我希望声明一个类邮箱,其中可以注册onChange事件

class Mailbox {
    std::function<void(int,int)> onChange;
};
Run Code Online (Sandbox Code Playgroud)

这样,调用者可以注册一个lambda onChange处理程序,它可以捕获这个或其他对处理事件很重要的变量.

由于这是API的一部分,我想为Mailbox的用户提供最大的灵活性,以提供函数指针,lambda或functor.

我已经设法找到一个看起来特别低开销的一个很好的实现,std::function并且正是我需要的,除了它涉及动态内存.

如果你看下面的代码,动态内存只用在一个地方,它看起来完全作用于被模板化的对象,向我建议它的大小应该在编译时知道.

任何人都可以帮助我理解如何重构这个实现,以便它是完全静态的,并删除使用new/malloc?我无法理解为什么CallableT的大小在编译时无法计算.

下面的代码(不适合胆小的人).注意,它使用make_unique/ unique_ptr但可以很容易地用new和*替换它们并且我已成功测试了该用例.

#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();
}
Run Code Online (Sandbox Code Playgroud)

编辑:添加了关于STL的说明

Quu*_*one 5

您要查找的内容的名称是“就地function”。目前至少存在一种非常好的实现:

tj::inplace_any<Size, Align>如果您需要/想要 的语义,还有any.


Rad*_*ski 2

尝试这个:

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;
};
Run Code Online (Sandbox Code Playgroud)

请记住,这类技巧往往有点脆弱。

在这种情况下,为了避免内存分配,我使用了假定最大大小的无符号 char[] 数组 - CallableT 的 max 以及指向函数的指针、指向成员函数的指针和 lambda 对象。函数和成员函数指针的类型并不重要,因为标准保证,对于所有类型,这些指针都具有相同的大小。Lambda 应该是指向对象的指针,但如果由于某种原因不是,并且它的大小会根据 lambda 类型而变化,那么你就不走运了。首先 callable_ 使用新的正确的 CallableT 类型进行初始化。然后,当您尝试调用时,我使用 callable_ 的开头作为指向 ICallable 的指针。这一切都是标准安全的。

请记住,您复制 naive_function 对象,它的模板参数 T 的复制运算符不会被调用。

更新:一些改进(至少尝试强制对齐)+添加复制构造函数/复制赋值。