Некоторые источники в интернете (в частности этот) говорит, что std :: function использует оптимизацию с малым замыканием, например он не выделяет кучу, если размер закрытия меньше некоторого объема данных (ссылка выше указывает на 16 байтов для gcc)
Так что я начал копаться в заголовках g ++
Похоже, применена ли такая оптимизация, определяется этим блоком кода в «функциональном» заголовке (g ++ 4.6.3)
static void
_M_init_functor(_Any_data& __functor, _Functor&& __f)
{ _M_init_functor(__functor, std::move(__f), _Local_storage()); }
и некоторые строки внизу:
static void
_M_init_functor(_Any_data& __functor, _Functor&& __f, true_type)
{ new (__functor._M_access()) _Functor(std::move(__f)); }
static void
_M_init_functor(_Any_data& __functor, _Functor&& __f, false_type)
{ __functor._M_access<_Functor*>() = new _Functor(std::move(__f)); }
};
например, если _Local_storage () имеет значение true_type, то вызывается позиция размещения нового, в противном случае — обычный новый
определение _Local_storage является следующим:
typedef integral_constant<bool, __stored_locally> _Local_storage;
и __stored_locally:
static const std::size_t _M_max_size = sizeof(_Nocopy_types);
static const std::size_t _M_max_align = __alignof__(_Nocopy_types);
static const bool __stored_locally =
(__is_location_invariant<_Functor>::value
&& sizeof(_Functor) <= _M_max_size
&& __alignof__(_Functor) <= _M_max_align
&& (_M_max_align % __alignof__(_Functor) == 0));
и наконец: __is_location_invariant:
template<typename _Tp>
struct __is_location_invariant
: integral_constant<bool, (is_pointer<_Tp>::value
|| is_member_pointer<_Tp>::value)>
{ };
Так. насколько я могу судить, тип замыкания не является ни указателем, ни указателем на член. Чтобы убедиться, что я даже написал небольшую тестовую программу:
#include <functional>
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "max stored locally size: " << sizeof(std::_Nocopy_types) << ", align: " << __alignof__(std::_Nocopy_types) << std::endl;
auto lambda = [](){};
typedef decltype(lambda) lambda_t;
std::cout << "lambda size: " << sizeof(lambda_t) << std::endl;
std::cout << "lambda align: " << __alignof__(lambda_t) << std::endl;
std::cout << "stored locally: " << ((std::__is_location_invariant<lambda_t>::value
&& sizeof(lambda_t) <= std::_Function_base::_M_max_size
&& __alignof__(lambda_t) <= std::_Function_base::_M_max_align
&& (std::_Function_base::_M_max_align % __alignof__(lambda_t) == 0)) ? "true" : "false") << std::endl;
}
и вывод:
max stored locally size: 16, align: 8
lambda size: 1
lambda align: 1
stored locally: false
Итак, у меня следующие вопросы: всегда ли инициализация std :: function с lambda приводит к выделению кучи? или я что-то упустил?
Начиная с GCC 4.8.1, std :: function в libstdc ++ оптимизируется только для указателей на функции и методы. Таким образом, независимо от размера вашего функтора (включая лямбды), инициализация функции std :: из него запускает выделение кучи. К сожалению, пользовательские распределители также не поддерживаются.
Visual C ++ 2012 и LLVM libc ++ избегают выделения для любого достаточно маленького функтора.
Обратите внимание, что для этой оптимизации в вашем функторе должно быть выполнено std :: is_nothrow_move_constructible. Это должно поддерживать noexcept std :: function :: swap (). К счастью, лямбды удовлетворяют этому требованию, если все захваченные значения соответствуют.
Вы можете написать простую программу для проверки поведения различных компиляторов:
#include <functional>
#include <iostream>
// noexpect missing in MSVC11
#ifdef _MSC_VER
# define NOEXCEPT
#else
# define NOEXCEPT noexcept
#endif
struct A
{
A() { }
A(const A&) { }
A(A&& other) NOEXCEPT { std::cout << "A(A&&)\n"; }
void operator()() const { std::cout << "A()\n"; }
char data[FUNCTOR_SIZE];
};
int main()
{
std::function<void ()> f((A()));
f();
// prints "A(A&&)" if small functor optimization employed
auto f2 = std::move(f);
return 0;
}
Могу поспорить, если вы добавили это:
std::cout << "std::__is_location_invariant: " << std::__is_location_invariant<lambda_t>::value << std::endl;
вы бы вернулись
std::__is_location_invariant: 0
По крайней мере вот что идеон говорит.
Распределение std :: function — это деталь реализации; однако в последний раз я проверял, 12 байтов — это максимальный размер функтора для msvc, 16 для gcc, 24 для boost + msvc.