c++ - g++:用闭包类型初始化的 std::function 总是使用堆分配?

标签 c++ lambda c++11 std-function

Internet 上的一些来源(特别是 this one)说 std::function 使用小闭包优化,例如如果闭包大小小于一定数量的数据,它不会分配堆(上面的链接表示 gcc 为 16 字节)

所以我深入研究了 g++ header

看起来是否应用这种优化是由“功能” header (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,则调用 placement-new,否则 - regular new

_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

所以,我的问题如下:使用 lambda 初始化 std::function 是否总是导致堆分配?还是我遗漏了什么?

最佳答案

从 GCC 4.8.1 开始,libstdc++ 中的 std::function 仅优化指向函数和方法的指针。因此,无论仿函数(包括 lambda 表达式)的大小如何,从中初始化 std::function 都会触发堆分配。不幸的是,也不支持自定义分配器。

Visual C++ 2012 和 LLVM libc++ 确实避免为任何足够小的仿函数分配内存。

请注意,要使此优化生效,您的仿函数应满足 std::is_nothrow_move_constructible。这是为了支持 noexcept std::function::swap()。幸运的是,如果所有捕获的值都符合要求,lambda 就可以满足这一要求。

您可以编写一个简单的程序来检查各种编译器的行为:

#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;
}

关于c++ - g++:用闭包类型初始化的 std::function 总是使用堆分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12452022/

相关文章:

C++、信号和线程

c++ - const 指针或引用的位置

c++ - Visual Studio 2013 中右值引用的初始化捕获

java - 如何删除用作监听器的 lambda 表达式/方法句柄?

c++ - C++14 中纯右值的 Cv 限定

c++ - 将枚举类传播到更高级别的类

c++ - 64位和32位无符号整数双向映射的高效实现

Java 8表达式从列表中填充列表,其中包含另一个列表

c++ - 将静态成员函数作为模板参数传递

c++ - 在没有任何构造函数的类的情况下,C++11 中自动生成的类成员函数