10

インターネット上の一部の情報源 (具体的にはthis one ) によると、std::function は小さなクロージャーの最適化を使用します。たとえば、クロージャーのサイズがデータの量よりも小さい場合はヒープを割り当てません (上記のリンクは gcc の 16 バイトを示しています)。

だから私は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 の場合、placement-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

だから、私の質問は次のとおりです: std::function をラムダで初期化すると、常にヒープ割り当てが発生しますか? または私は何かを逃していますか?

4

3 に答える 3

7

GCC 4.8.1 の時点で、libstdc++ の std::function は、関数とメソッドへのポインターに対してのみ最適化します。したがって、ファンクター (ラムダを含む) のサイズに関係なく、それから 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;
}
于 2013-06-24T14:51:24.060 に答える
2

これを追加した場合、私は賭けます:

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

あなたは戻ってきます:

std::__is_location_invariant: 0

少なくともイデオンはそう言っている

于 2012-09-17T01:31:07.627 に答える
1

std::function の割り当ては実装の詳細です。ただし、最後に確認したところ、msvc の最大ファンクター サイズは 12 バイト、gcc は 16 バイト、boost+msvc は 24 バイトです。

于 2012-09-17T01:05:37.737 に答える