如何实现像std :: function这样的自毁类型擦除类?

时间:2016-03-12 00:29:05

标签: c++ type-erasure self-destruction

我想了解std::function的实施是如何运作的。为简单起见,让我们考虑不带参数的只移动函数。

我知道std::function通过典型的类型擦除技术删除了其目标类型:

template<class Result>
struct function
{
  public:
    template<class Function>
    function(Function&& f)
      : f_(std::make_unique<callable_base>(std::forward<Function>(f)))
    {}

    // XXX how to implement constructor with allocator?
    template<class Alloc, class Function>
    function(const Alloc& alloc, Function&& f);

    Result operator()() const
    {
      return (*f_)();
    }

  private:
    struct callable_base
    {
      virtual Result operator()() const = 0;
      virtual ~callable_base(){}
    };

    template<class Function>
    struct callable
    {
      mutable Function f;

      virtual Result operator()() const
      {
        return f;
      }
    };

    // XXX what should the deleter used below do?
    struct deleter;

    std::unique_ptr<callable_base, deleter> f_;
};

我想扩展此类型的功能以支持自定义分配。我需要删除分配器的类型,但使用std::unique_ptr很难做到这一点。赋予unique_ptr的自定义删除器需要知道给予构造函数的Function的具体类型,以便能够正确地释放其存储。我可以使用另一个unique_ptr来键入删除删除器,但该解决方案是循环的。

似乎callable<Function>需要释放自己。这样做的正确方法是什么?如果我在callable<Function>的析构函数内部解除分配,这似乎太早了,因为它的成员仍然活着。

3 个答案:

答案 0 :(得分:2)

我认为不可能以便携方式执行此操作,仅依赖 在提供的分配器上进行内存管理。

我一直在寻找std::shared_ptr的实现,因为它支持type erasure also for it's deleter and allocator (see overload 6):在an sketch implementation I found around here中,有一个辅助对象用于存储这些副本,但此对象是使用{分配的{1}}并使用operator new释放,从而绕过提供的分配器和删除器。

我正在考虑使用分配器的临时副本(在堆栈上)来释放存储的分配器(从中进行复制)以及存储的对象。问题是:如果不使用operator delete / new,在不知道类型的情况下如何获得副本?不幸的是,这是covariance is ruled out(需要返回一个指针)。

现在,我们开始使用非标准解决方案:如果您习惯使用allocavariable length arrays,那么您可以使用删除器在堆栈上创建足够大的内存区域让存储的分配器创建自己的副本到该内存中。这个堆栈分配(因此自动存储持续时间)副本然后可以释放存储的分配器和存储的对象,并最终被删除函数破坏(作为所有这一点,它不知道分配器的具体类型)。粗略草图:

delete

那就是说,如果 是一种在标准C ++中执行此操作的方法而不依赖于另一个动态内存源而不是提供的分配器,我将非常高兴知道它! :)

答案 1 :(得分:1)

std::function在C ++ 17中丢失了分配器,部分原因是类型擦除分配器出现问题。但是,一般模式是将分配器重新绑定到您用于执行类型擦除的任何类型,将原始分配器存储在类型擦除的东西中,并在删除类型擦除事物时再次重新绑定分配器。

template<class Ret, class... Args>
struct Call_base {
    virtual Ret Call(Args&&...);
    virtual void DeleteThis();
protected:
    ~Call_base() {}
};

template<class Allocator, class Fx, class Ret, class... Args>
struct Call_fn : Call_base<Ret, Args...> {
    Allocator a;
    decay_t<Fx> fn;

    Call_fn(Allocator a_, Fx&& fn_)
        : a(a_), fn(forward<Fx>(fn_))
        {}

    virtual Ret Call(Args&& vals) override {
        return invoke(fn, forward<Args>(vals)...);
    }
    virtual void DeleteThis() override {
        // Rebind the allocator to an allocator to Call_fn:
        using ReboundAllocator = typename allocator_traits<Allocator>::
            template rebind_alloc<Call_fn>;
        ReboundAllocator aRebound(a);
        allocator_traits<ReboundAllocator>::destroy(aRebound, this);
        aRebound.deallocate(this, 1);
    }
};

template<class Allocator, class Fx, class Ret, class... Args>
Call_base<Ret, Args...> * Make_call_fn(Allocator a, Fx&& fn) {
    using TypeEraseType = Call_fn<Allocator, Fx, Ret, Args...>;
    using ReboundAllocator = typename allocator_traits<Allocator>::
        template rebind_alloc<TypeEraseType>;
    ReboundAllocator aRebound(a);
    auto ptr = aRebound.allocate(1); // throws
    try {
        allocator_traits<ReboundAllocator>::construct(aRebound, ptr, a, forward<Fx>(fn));
    } catch (...) {
        aRebound.deallocate(ptr, 1);
        throw;
    }

    return ptr;
}

答案 2 :(得分:0)

这是我想出的近似值。我不相信它完全正确,但它适用于我的用例。

这个想法是使用&#34; no-op&#34;使用$( "td.clickable" ).click(function() { 删除。删除器调用对象的析构函数但不释放其存储空间。该对象通过回调在其析构函数中自行释放。

unique_ptr

如果给定的分配器成为template<class Result> struct function { public: template<class Function> function(Function&& f) : f_(std::make_unique<callable_base>(std::forward<Function>(f))) {} template<class Alloc, class Function> function(const Alloc& alloc, Function&& f) : f_(allocate_unique(alloc, std::forward<Function>(f))) {} Result operator()() const { return (*f_)(); } private: struct callable_base { // a deallocation callback to use within our destructor using deallocate_function_type = void(*)(callable_base*); deallocate_function_type deallocate_function; template<class Function> callable_base(Function callback) : deallocate_function(callback) {} virtual Result operator()() const = 0; virtual ~callable_base() { // deallocate this object's storage with the callback deallocate_function(this); } }; template<class Alloc, class Function> struct callable : callable_base { mutable Function f; callable(Function&& f) : callable_base(deallocate), f(std::forward<Function>(f)) {} virtual Result operator()() const { return f; } static void deallocate(callable_base* ptr) { // upcast to the right type of pointer callable* self = static_cast<callable*>(ptr); // XXX it seems like creating a new allocator here is cheating // instead, we should use some member allocator, but it's // not clear where to put it Alloc alloc; alloc.deallocate(self); } }; struct self_deallocator_deleter { template<class T> void operator()(T* ptr) const { // call T's destructor but do not deallocate ptr ptr->~T(); } }; template<class Alloc, class Function> static std::unique_ptr<callable_base, self_deallocator_deleter> allocate_unique(const Alloc& alloc, Function&& f) { // allocate and construct the concrete callable object auto f_ptr = std::allocator_traits<Alloc>::allocate(alloc, 1); std::allocator_traits<Alloc>::construct(f_ptr, std::forward<Function>(f)); // return the pointer through a unique_ptr return std::unique_ptr<callable_base,self_deallocator_deleter>(f_ptr); } std::unique_ptr<callable_base, self_deallocator_deleter> f_; }; 对象的成员而不是在callable内动态创建的新分配器对象,则解决方案会更好。问题是我们无法使分配器成为callable::deallocate的成员,因为callable对象在调用callable时不再存在。