在c ++

时间:2017-07-13 18:44:51

标签: c++ lambda type-erasure

我在内存受限的嵌入式环境中工作,其中不建议使用malloc / free new / delete,并且我尝试使用std :: function模式注册回调。我无法访问目标代码中的任何STL方法,所以我不得不自己复制一些STL功能。函数指针不适合我,因为调用者必须有捕获。

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

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

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

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

我设法找到一个std::function的{​​{1}},它看起来特别低开销,除了它涉及动态内存之外,它正是我需要的。

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

任何人都可以帮助我理解如何重构这个实现,以便它是完全静态的并且不使用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();
}

编辑:在STL上添加了澄清

3 个答案:

答案 0 :(得分:3)

您正在寻找的名称是&#34;就地function&#34;。至少目前存在一个非常好的实现:

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

答案 1 :(得分:1)

试试这个:

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 []数组假设的最大大小 - CallableT的max,指向函数的指针,指向成员函数的指针和lambda对象。作为标准保证,函数和成员函数的指针类型无关紧要,对于所有类型,指针将具有相同的大小。 Lambda应该是指向对象的指针,但是如果由于某种原因而且它的大小将根据lambda类型而改变,那么你就不幸了。 首先使用新的和正确的CallableT类型初始化callable_。然后,当你尝试调用时,我使用callable_的开头作为指向ICallable的指针。这一切都是标准的安全。

请记住,你复制了naive_function对象,它的模板参数T的复制操作符没有被调用。

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

答案 2 :(得分:1)

让我先说一下这个答案,说存储一般的可调用内容在内存管理方面面临一个有趣的选择。是的,我们可以在编译时推断出任何可调用的大小,但是我们不能在没有内存管理的情况下将任何可调用的内容存储到同一个对象中。那是因为我们自己的对象需要具有独立于其应该存储的可调用大小的大小,但是这些可以是任意大的。

将这个推理放在一个句子中:我们的类(及其接口)的布局需要在不知道所有调用者的情况下进行编译。

这给我们留下了3个选择

  1. 我们接受内存管理。我们动态地复制可调用对象并通过唯一指针(std或boost)或通过自定义调用new和delete来正确管理该内存。这就是您找到的原始代码所做的事情,也由std::function完成。
  2. 我们只允许某些callables。我们在对象中创建一些自定义存储来保存某些形式的可调用对象。此存储具有预定大小,我们拒绝任何不能遵守此要求的可调用(例如,通过static_assert)。请注意,这不一定限制可能的呼叫者集。相反,接口的任何用户都可以设置代理类,只保留指针但转发调用操作符。我们甚至可以自己提供这样的代理类作为库的一部分。但这只不过是将分配点从function实现内部转移到外部。它仍然值得一试,@radosław-cybulski在他的回答中最接近这一点。
  3. 我们不做内存管理。我们可以设计我们的界面,它故意拒绝取得给予它的可调用的所有权。这样,我们不需要内存管理,这部分完全取决于我们的调用者。这就是我将在下面给出的代码。它不是std::function的替代品,而是我认为只有你想要的通用,无分配,可复制类型的唯一方式。
  4. 以下是可能性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