2016-03-12 3 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, чтобы напечатать стирание deleter, но это решение является круглым.

Кажется, что callable<Function> необходимо освободить себя. Каков правильный способ сделать это? Если я освобожусь от деструктора callable<Function>, это кажется слишком ранним, потому что его члены все еще живы.

+0

Обратите внимание, что в C++ 17 удаляется поддержка распределителя для 'std :: function'. – Brian

ответ

1

std::function потерял свои распределители в C++ 17 частично из-за проблем с типами стертых распределителей. Тем не менее, общая схема состоит в том, чтобы повторно переназначить распределитель любым типом, который вы используете, чтобы выполнить стирание типа, сохранить исходный распределитель в типе стираемой вещи и снова повторно переназначить распределитель при удалении стираемого типа.

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 и free'd с operator delete, таким образом, минуя входящий в комплект поставки распределитель и дебетер.

Я думал об использовании временной копии (в стеке) распределителя, чтобы освободить как хранимый распределитель (из которого была сделана копия), так и сохраненный объект. Проблема в следующем: как получить копию, когда вы не знаете тип, без использования new/delete? К сожалению, covariance is ruled out этим (требуется, чтобы вернуть указатель).

А теперь мы перейдем к нестандартным решениям: Если вы комфортно, используя либо alloca или variable 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

Приблизительно я придумал. Я не считаю, что это совершенно правильно, но это работает для моего случая использования.

Идея заключается в использовании «no-op» deleter с unique_ptr. Делектор вызывает деструктор объекта, но не освобождает его. Объект self-deallocates внутри своего деструктора через обратный вызов.

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 больше не жив в точке, где вызывается callable::deallocate.

Смежные вопросы