2016-03-12 32 views
1

我想了解std::function的實現是如何工作的。爲了簡單起見,我們考慮不帶參數的移動功能。像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>的析構函數中釋放內存,那似乎爲時過早,因爲它的成員仍然活着。

+0

請注意,分配器對'std :: function'的支持正在C++ 17中被刪除。 – Brian

回答

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

我不認爲有可能以便攜的方式做到這一點,依靠只有上提供的內存管理分配器。

我正在尋找的std::shared_ptr實施,因爲它支持type erasure also for it's deleter and allocator (see overload 6):在an sketch implementation I found around here有使用的是,這些副本存儲的輔助對象,但該目的使用operator new分配並用operator delete free'd,從而繞過供給分配器和刪除器。

我正在考慮使用分配器的臨時副本(在堆棧上)來釋放存儲的分配器(從中進行復制)以及存儲的對象。問題是:如果您不知道類型,如何在不使用new/delete的情況下獲得副本?不幸的是,covariance is ruled out被這個(要求返回一個指針)。

而現在,我們得到的非標準解決方案:如果您選擇使用allocavariable length arrays,那麼你可以有一個在棧上創建了一個足夠大的存儲區缺失者,讓存儲分配器創建一個副本很舒適自己進入那個記憶。這個分配的堆棧(因此自動存儲持續時間)副本可以釋放存儲的分配器和存儲的對象,並最終由deleter函數銷燬(作爲所有這些的關鍵點,它不知道分配器的具體類型)。草圖:

struct aux_base { 
    // also provide access to stored function 
    virtual size_t my_size(void) const = 0; 
    virtual aux_base * copy_in(void * memory) const = 0; 
    virtual void free(void * ptr) = 0; 
    virtual ~aux_base() {} 
}; 

template<class Alloc, class Function> 
struct aux : public aux_base { 
    // Store allocator and function here 

    size_t my_size(void) const { 
    return sizeof(*this); 
    } 
    aux_base * copy_in(void * memory) const { 
    // attention for alignment issues! 
    return new (memory) aux(*this); 
    } 
    void free(void * ptr) { 
    aux * stored = reinterpret_cast<aux *>(ptr); 
    // do your stuff 
    } 
}; 

void deleter_for_aux(aux_base * ptr) { 
    char memory[ptr->my_size()]; 
    aux_base * copy = ptr->copy_in(memory); 
    copy->free(ptr); 
    copy->~aux_base(); // call destructor 
} 

這就是說,如果有一個辦法做到這一點的C++標準,而不依賴於除附帶分配器我會很高興知道它的另一個動態內存源! :)

+0

>當你不知道類型時,如何在沒有使用新/刪除的情況下獲得副本? 你有一個虛擬的函數調用爲你做。 –

+0

@BillyONeal你如何將該虛擬函數內的副本傳遞給其調用者?沒有動態(堆)內存? –

+0

什麼副本? 'std :: function'從不需要分配器副本。 –

0

這是我想出的近似值。我不相信這是完全正確的,但它適用於我的用例。

這個想法是對unique_ptr使用「no-op」刪除器。刪除者調用對象的析構函數,但不釋放其存儲。該對象通過回調在其析構函數中自釋放。

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::deallocatecallable對象,而不是一個新的分配對象的成員。問題是我們無法使分配器成爲callable的成員,因爲callable對象在調用callable::deallocate時不再有效。

相關問題