Можно ли принимать вывод `std::bind` напрямую как значение, без преобразования в std::function?

В этом ответе говорится, что std::bind возвращает объект по значению, а этот комментарий подразумевает, что присвоение std::function вызовет выделение кучи для хранения значение, возвращаемое std::bind.

Есть ли способ избежать этого выделения кучи и передать возвращаемое значение std::bind другой функции напрямую по значению?

Если да, то чем сигнатура метода заменит std::function?


Чтобы быть более явным, у меня есть функция, подобная следующей.

void runThisFunction(std::function<void()> func);

Предположим, есть функция foo со следующей сигнатурой.

void foo(int a, int b);

Теперь я бы вызвал runThisFunction следующим образом.

runThisFunction(std::bind(foo, 1, 2));

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

Можно ли заменить std::function каким-то другим объявлением, которое бы получало вывод std::bind напрямую по значению, тем самым избегая динамического выделения памяти?


person merlin2011    schedule 14.04.2016    source источник
comment
template <class T> void runThisFunction(T func) считается?   -  person Piotr Skotnicki    schedule 14.04.2016
comment
@PiotrSkotnicki, кажется, это может сработать. Он страдает от обычной проблемы с шаблонами (генерация одной копии кода для каждого типа), но я полагаю, что ничего не поделаешь.   -  person merlin2011    schedule 14.04.2016
comment
Не уверен, почему этот вопрос был отклонен. Мне просто не приходило в голову использовать шаблон для решения этой проблемы, хотя задним числом это кажется очевидным.   -  person merlin2011    schedule 14.04.2016
comment
Если вам не нравятся шаблоны, а вызывающие программы недостаточно различаются, чтобы в них нуждаться, вы можете попробовать void runThisFunction(decltype(std::bind(foo, 1, 2))& func); — я подозреваю, что вы сможете вызывать его с любым вызовом std::bind(), где статические типы аргументов соответствуют foo, 1 и 2, но вам придется покопаться в Стандарте, чтобы убедиться, что это гарантировано.   -  person Tony Delroy    schedule 14.04.2016
comment
Как часто вы запускаете эту функцию? На эквиваленте для каждого пикселя в кадре или аналогичном? (например, много миллионов раз в секунду?)   -  person Yakk - Adam Nevraumont    schedule 14.04.2016


Ответы (4)


Можно ли заменить std::function каким-то другим объявлением, которое бы получало вывод std::bind напрямую по значению, тем самым избегая динамического выделения памяти?

Да, это так; но тип, возвращаемый std::bind, не указан, поэтому вам нужно будет использовать шаблон для захвата типа;

template <typename F>
void runThisFunction(F func);

О распределении памяти...

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

Можно использовать динамическую память (но не всегда), это зависит от размера функтора, связанного с std::function, и качество реализации.

Кроме того, в спецификации C++ есть этот §20.12.12.2.1/11. ;

[Примечание: реализациям рекомендуется избегать использования динамически выделяемой памяти для небольших вызываемых объектов, например, где f — это объект, содержащий только указатель или ссылку на объект и указатель на функцию-член. — примечание в конце]

Я бы не слишком беспокоился о распределении памяти, даже если оно есть. Если код не критичен к производительности и вы не измеряли его как таковой, требуемая косвенность не должна быть проблемой.

Имейте в виду, что в вашем случае foo, привязанный к bind, является указателем, и вполне вероятно, что в любом случае не будет динамического выделения памяти.


Я начал смотреть на это, потому что я измерял промахи кеша при преобразовании из-за неожиданной медлительности, обнаруженной с помощью инструментов.

Итак, у вас есть определенные опасения по поводу производительности... есть альтернативы использованию std::bind в паре с std::function. std::bind полезное связующее общего назначения, но это не значит, что оно будет достаточно эффективным - сделайте свое собственное. Пользовательский функтор может быть более производительным. Реализация на основе лямбда также была бы полезна. Не забывайте также, что функцию foo можно использовать и с std::function, и тогда вы полностью отказываетесь от функтора/биндера (осторожно, сигнатуры должны совпадать).


Небольшое примечание о том, насколько «маленьким» должен быть объект, чтобы оптимизация «маленького объекта», упомянутая в приведенной выше цитате, вступила в силу, по-видимому, немного различается в зависимости от реализации библиотеки.

Здесь на coliru (libstdc++) размер аргумента для std::function должен быть 16 байт или меньше, на MSVC ограничение составляет 32 байта (обе эти платформы выглядят как 32-разрядные платформы) . С 64-битной компиляцией clang++ (libc++) это ограничение составляет 24 байта... Это действительно зависит от реализации, сколько места они будут выделять, прежде чем необходимо будет сделать new распределения.

Я не уверен, насколько критична производительность, но вычисление этого предела для ваших целей также может быть выполнено, а затем применена оптимизация, чтобы аргументы для std::function оставались ниже этих пределов; например используя указатель или ссылку (также std::ref) на struct для аргументов (но нужно следить за тем, чтобы они не оставались висящими).

person Niall    schedule 14.04.2016
comment
Я начал смотреть на это, потому что я измерял промахи кеша при преобразовании из-за неожиданной медлительности, обнаруженной с помощью инструментов. Я думаю, что этот ответ сработает, но я соглашусь после того, как проверю его завтра. Спасибо! - person merlin2011; 14.04.2016
comment
Этот ответ работает, но создание шаблона принимающей функции оказывается более болезненным, чем я ожидал, потому что это означает перенос большого количества состояний в файл заголовка, который я изначально аккуратно спрятал в файле cc. - person merlin2011; 14.04.2016
comment
Это один из недостатков, к которому склонны неуказанные типы. Я бы сказал, что вы могли бы исследовать редизайн, но это совсем другая проблема, и это может быть излишним. - person Niall; 14.04.2016

Если производительность важна, но вам по-прежнему нужен конкретный интерфейс, рассмотрите возможность привязки к лямбде, а не к объекту связывания (через std::bind).

Я провел этот тест на gcc 5.3 (libstdc++, -O2)

#include <functional>

void foo(int, int);

void runThisFunction(std::function<void()> func);

void test()
{
  runThisFunction(std::bind(&foo, 1, 2));
}

void test1()
{
  runThisFunction([]{ foo(1, 2); });
}

test() приводит к вызову new. Однако небольшая оптимизация функции в std::function способна обнаружить, что лямбда в test1() достаточно мала, и вызов new не передается в код:

(примечание: код обработки исключений удален для ясности):

std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation):
        testl   %edx, %edx
        je      .L3
        cmpl    $1, %edx
        jne     .L2
        movq    %rsi, (%rdi)
.L2:
        xorl    %eax, %eax
        ret
.L3:
        movq    typeinfo for test1()::{lambda()#1}, (%rdi)
        xorl    %eax, %eax
        ret
std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&):
        movl    $2, %esi
        movl    $1, %edi
        jmp     foo(int, int)
test1():
        subq    $40, %rsp
        movq    %rsp, %rdi
        movq    std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&), 24(%rsp)
        movq    std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation), 16(%rsp)
        call    runThisFunction(std::function<void ()>)
        movq    16(%rsp), %rax
        testq   %rax, %rax
        je      .L7
        movl    $3, %edx
        movq    %rsp, %rsi
        movq    %rsp, %rdi
        call    *%rax
.L7:
        addq    $40, %rsp
        ret
typeinfo for test1()::{lambda()#1}:
typeinfo name for test1()::{lambda()#1}:
person Richard Hodges    schedule 14.04.2016
comment
Гарантия отсутствия выделения в std::function для указателя и reference_wrapper (и рекомендуется для небольших объектов). Ваша лямбда здесь ничего не фиксирует (поэтому может быть преобразована в указатель функции). С захватом все может быть иначе. - person Jarod42; 14.04.2016
comment
@ Jarod42 Jarod42 да, оптимизация небольших функций будет иметь предел своей агрессивности в зависимости от реализации стандартной библиотеки. Тем не менее, я не могу строить предположения о будущих сценариях, которые могут быть у ОП - это тот, который он представил. - person Richard Hodges; 14.04.2016
comment
Интересно знать, что при некоторых обстоятельствах лямбда работает лучше, учитывая тот факт, что я переключился на std::bind именно потому, что лямбда не работала в gcc. - person merlin2011; 14.04.2016
comment
@ merlin2011 интересно, но вы видели обходной путь? передать вызываемый по значению? Я бы подумал, что копирование-элизион будет означать, что это не медленнее. - person Richard Hodges; 14.04.2016
comment
@RichardHodges, я использовал std::bind специально, потому что лямбда с varargs не компилировалась. - person merlin2011; 14.04.2016

Используя ссылочную оболочку, вы должны избегать выделения кучи:

auto f = std::bind(foo, 1, 2);
runThisFunction(std::ref(f));

Это связано с тем, что reference_wrapper — это небольшой объект, а std::function рекомендуется избегать размещения под него (см. [func.wrap.func.con]):

Примечание. В реализациях рекомендуется избегать использования динамически выделяемой памяти для небольших вызываемых объектов, например, где целью f является объект, содержащий только указатель или ссылку на объект и указатель на функцию-член.

Это будет работать только в том случае, если runThisFunction() не хранит значение std::function для вызова после окончания жизни f.

person ysdx    schedule 14.04.2016
comment
Аккуратная альтернатива с std::ref. Я не думаю, что простой вызов функции - это все, что делает runThisFunction, поэтому это должно сопровождаться оговоркой, что привязка f должна оставаться активной, по крайней мере, до тех пор, пока runThisFunction требует std::function (и все остальное, что runThisFunction подает функция, например, какой-то список). - person Niall; 14.04.2016

очень просто (по крайней мере, для gcc):

int someFunc(int a, int b) { return a+b; }
//....
int b = (std::bind(someFunc,3,4))(); // returns 7
person Andrei R.    schedule 14.04.2016