Как я могу создать std::function с настраиваемым распределителем?

Чтобы сохранить некоторый код, скажем, у меня есть собственный распределитель с именем MyAlloc, который я успешно использовал с std::vector<int> следующим образом:

std::vector<int,MyAlloc<int>> vec;

теперь я хочу сохранить лямбду в std::function с помощью пользовательского распределителя, как мне это сделать?

Моя неудачная попытка:

int i[100];
std::function<void(int)> f(MyAlloc<void/*what to put here?*/>{},[i](int in){
    //...
});

Обновление: распределители в std::function были устарело


person odinthenerd    schedule 13.01.2014    source источник
comment
Я не вижу поддержки распределителя здесь для std::function .   -  person RedX    schedule 13.01.2014
comment
@RedX en.cppreference.com/w/cpp/utility/functional/ функция/функция предполагает, что это возможно   -  person odinthenerd    schedule 13.01.2014
comment
@RedX С документами все в порядке. Обратите внимание, что PorkyBrain связался с документацией для конструктора std::function, а вы связались с самим классом. Распределитель нужен только для построения std::function, а не для его использования. Следовательно, только конструктор шаблонен для типа распределителя, но не сам класс.   -  person ComicSansMS    schedule 13.01.2014
comment
@PorkyBrain Какой компилятор вы используете? Я только что заметил, что VC, похоже, перепутал порядок аргументов конструктора здесь.   -  person ComicSansMS    schedule 13.01.2014
comment
@ComicSansMS MSVC2013   -  person odinthenerd    schedule 13.01.2014
comment
Есть предложение удалить поддержку распределителя из std::function: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r0.html с полезным списком известных проблем   -  person Andriy Tylychko    schedule 31.07.2017


Ответы (2)


Согласно стандарту, в качестве первого аргумента необходимо указать тип тега. чтобы указать, что вы хотите использовать пользовательский распределитель:

std::function<void(int)> f(std::allocator_arg, MyAlloc<char>{}, [i](int in){
    //...
});

Как указал @Casey и @Potatoswatter в комментариях тип аргумента шаблона, переданный распределителю, не имеет значения, если это тип объекта. Так что char здесь нормально.

Обновление для C++17: оказывается, что поддержка распределителя для std::function имеет ряд фундаментальных проблем, которые приводят к является устаревшим в C++17. Если вы, тем не менее, настаиваете на его использовании, обязательно тщательно проверьте свою реализацию, прежде чем делать это. Стандартная библиотека GCC никогда не реализовывала эти функции, но даже если ваша стандартная библиотека это делает, она может вести себя не так, как вы ожидаете.

person ComicSansMS    schedule 13.01.2014
comment
Спасибо за вашу помощь, кажется, это работает на MSVC2013, вы уверены, что параметр шаблона для MyAlloc должен быть недействительным? - person odinthenerd; 13.01.2014
comment
@PorkyBrain: интуиция => любой тип подходит, потому что std::function в любом случае перепривязывает распределитель к другому (внутреннему) типу. - person Matthieu M.; 13.01.2014
comment
@PorkyBrain Почти наверняка не имеет значения, какой параметр вы используете для распределителя: std::function немедленно перепривязывает распределитель к любому внутреннему типу, который ему необходимо фактически выделить, а распределитель, который вы передаете конструктору, будет использоваться только для копирования-конструкции этот распределитель отскока. - person Casey; 13.01.2014
comment
спасибо, ребята, я тоже так думал, но не был уверен. Я не хочу в конечном итоге зависеть от деталей реализации MSVC - person odinthenerd; 13.01.2014
comment
@PorkyBrain C++11 §[func.wrap.func.con]/1 состояния Когда вызывается любой конструктор функции, который принимает первый аргумент типа allocator_arg_t, второй аргумент должен иметь тип, соответствующий требованиям для распределителя (таблица 17.6.3.5). Копия аргумента распределителя используется для выделения памяти, если это необходимо, для внутренних структур данных сконструированного объекта function. Таким образом, подходит любой соответствующий тип распределителя. - person Casey; 13.01.2014
comment
void не является допустимым аргументом типа распределителя. Хотя std::allocator<void> определен, распределителям обычно требуются типы объектов. Поскольку подойдет любой произвольный тип объекта, лучше выбрать char. - person Potatoswatter; 03.06.2015
comment
@Potatoswatter Правда, void, вероятно, не лучший выбор для типа аргумента здесь, учитывая специализацию для std::allocator<void>. Соответственно изменил ответ. Спасибо, что указали! - person ComicSansMS; 03.06.2015
comment
На самом деле это заканчивается удалением в С++ 17. - person L. F.; 18.03.2020

Я понимаю, что на этот вопрос был дан правильный ответ, но даже после прочтения этой статьи и ответов я немного боролся с правильным синтаксисом, пытаясь перегрузить распределитель для std::function, который кросс-компилируется на X64, PS4 и Xbox One в VS2012.

Если читателю непонятно, вам нужно будет объявить класс распределителя в соответствии с комментарием Кейси. Хотя это довольно очевидно, если вы прочитали все ответы, что было неясно, так это то, как эти распределители передаются объекту, который не похож на большинство распределителей STL, которые я использовал ранее, которые принимают тип распределителя (не экземпляр). ) как часть спецификации типа.

Для std::function инстанцированный распределитель передается конструктору вашего объекта std::function, как это показано ComicSansMS выше.

Для использования этого с функцией-членом вместо лямбда-кода, показанного в этом примере, становится немного сложнее:

   #include <functional>

    MyAllocType g_myAlloc; // declared somewhere and globally instantiated to persist

    // sample of a member function that takes an int parameter
    class MyClassType
    {
    public:
        void TestFunction( int param )
        {
        }
    };

    MyClassType MyClass; // instantiated object

    // example without allocator
    // note the pointer to the class type that must precede function parameters since 
    // we are using a method. Also std::mem_fn is require to compile in VS2012 :/
    std::function<void(MyClassType*, int)> f( std::mem_fn( &MyClassType::TestFunction ) );

    // usage of function needs an instantiated object (perhaps there is a way around this?)
    f( &MyClass, 10 );

    // example with allocator
    std::function<void(MyClassType*, int)> f(std::allocator_arg, g_myAlloc, std::mem_fn( &MyClassType::TestFunction ) );

    // usage of function is the same as above and needs an instantiated object 
    f( &MyClass, 10 );

    //or a non member function, which is much cleaner looking
    void NonMemberFunction( int param )
    {
    }

    std::function<void(int)> f(std::allocator_arg, g_myAlloc, NonMemberFunction);

Надеюсь, это поможет людям, мне потребовалось больше времени, чем я хотел бы признать, чтобы заставить это работать, и, поскольку я использую этот сайт, я решил, что оставлю здесь комментарий, если только для меня, о том, как его использовать. . :)

2 заключительных вопроса к тем, кто умнее меня:

Вопрос. Можно ли включить распределитель как часть типа?

Вопрос. Есть ли способ использовать функцию-член без экземпляра объекта?

Чтобы обновить это, если вы решите передать один из этих объектов std::function в качестве параметра какой-либо другой функции, я обнаружил, что мне нужно использовать std::function::assign, иначе присваивание приводит к поверхностной копии. Это может быть проблемой, если вы пытаетесь передать его объекту с более длительным жизненным циклом, чем у оригинала.

Пример:

std::function<void(MyClassType*, int)> f(std::allocator_arg, g_myAlloc, std::mem_fn( &MyClassType::TestFunction ) );

void FunctionTakeParam( std::function<void(MyClassType*, int)> &FunctionIn )
{
    // this results in a reallocation using your allocator
    std::function<void(MyClassType*, int)> MyLocalFunction.assign( std::allocator_arg, g_myAlloc, FunctionIn ); 

    // the below results in a shallow copy which will likely cause bad things
    //std::function<void(MyClassType*, int)> MyLocalFunction( std::allocator_arg, g_myAlloc, FunctionIn ); 

    ...
}
person jeffdev    schedule 17.07.2014
comment
Нет абсолютно никакой причины включать распределитель в тип, зачем вам это делать? Можно передать функцию-член без экземпляра объекта, но ее нельзя вызвать без объекта, в этом даже нет смысла. struct person { void raise_hand(){} };, а затем вы звоните person::raise_hand(). Кто поднимает руку? - person Mooing Duck; 18.07.2014
comment
Почему? Потому что у вас может быть служебная функция-член, которая не имеет доступа к членам, но является частью класса. Или функция, которая является статической, не говоря уже о типичном случае использования, я просто никогда раньше не видел такого синтаксиса, и мне было любопытно. Что касается распределителя в типе, он предназначен для предотвращения неловкости, возникающей, когда вы начинаете передавать эти объекты. Я не могу сказать, передам ли я один из этих объектов std::function по значению, будет ли копия также использовать мой распределитель? Я бы предположил, что, вероятно, нет, поэтому наличие его в декалации типа может сделать использование более понятным. - person jeffdev; 18.07.2014
comment
Чтобы еще раз попытаться ответить, почему я хочу, чтобы это было в типе, вот как я перегружаю распределитель для std::string typedef std::basic_string‹char, std::char_traits‹char›, STLHeapAllocator‹char› › SampleHeapString; Обратите внимание, что я фактически нигде не создаю экземпляр распределителя. И любой объект, который я создаю, использует мой распределитель, верно? Или я сошел с ума и делаю это ужасно неправильно? Это тоже возможно :) - person jeffdev; 18.07.2014
comment
о, это оно? std::function (а также std::shared_ptr и некоторые другие) размещает в стеке только один объект. Таким образом, единственное, для чего может понадобиться распределитель, — это освобождение. Таким образом, он может легко использовать стирание типов без дополнительных накладных расходов. Он по-прежнему содержит экземпляр вашего распределителя, но ему может быть назначена другая функция с другим распределителем, и он будет переключать распределители во время выполнения (без накладных расходов). Таким образом, тип распределителя не влияет на тип функции. - person Mooing Duck; 18.07.2014