g++: std::function, инициализированный с типом закрытия, всегда использует выделение кучи?

В некоторых источниках в Интернете (в частности, в этом) говорится, что 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 с лямбдой приводит к выделению кучи? или я что-то упускаю?


person Alex I.    schedule 17.09.2012    source источник
comment
Я подтверждаю ваши выводы этой программой: ideone.com/kzae6U Вы можете проверить на clang (melpon.org/wandbox), что эта же программа выделяет память только для очень больших захватов...   -  person PiotrNycz    schedule 21.04.2015


Ответы (3)


Начиная с GCC 4.8.1, std::function в libstdc++ оптимизируется только для указателей на функции и методы. Таким образом, независимо от размера вашего функтора (включая лямбда-выражения), инициализация из него функции std::function вызывает выделение кучи. К сожалению, нет поддержки пользовательских распределителей.

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;
}
person Valentin Milea    schedule 24.06.2013

Бьюсь об заклад, если вы добавили это:

std::cout << "std::__is_location_invariant: " << std::__is_location_invariant<lambda_t>::value << std::endl;

вы получите обратно:

std::__is_location_invariant: 0

По крайней мере, так говорит ideone.

person MSN    schedule 17.09.2012
comment
Да, это в значительной степени следует из моего теста. Вопрос: это окончательно? Доктор Доббс ошибается, и у нас всегда есть выделение кучи? - person Alex I.; 17.09.2012
comment
@AlexI., это действительно зависит от компилятора. - person MSN; 17.09.2012

Выделение std::function — это деталь реализации; однако в последний раз я проверял, 12 байтов - это максимальный размер функтора для msvc, 16 для gcc, 24 для boost + msvc.

person adzm    schedule 17.09.2012
comment
adzm: да, это упоминается в ссылке в начале вопроса. Однако я не вижу, чтобы это было действительно так с g++ - person Alex I.; 17.09.2012