避免使用std :: function和member函数进行内存分配

bin*_*y01 24 c++ gcc

此代码仅用于说明问题.

#include <functional>
struct MyCallBack {
    void Fire() {
    }
};

int main()
{
    MyCallBack cb;
    std::function<void(void)> func = std::bind(&MyCallBack::Fire, &cb);
}
Run Code Online (Sandbox Code Playgroud)

使用valgrind进行的实验表明,分配的行在funclinux上使用gcc 7.1.1动态分配大约24个字节.

在真正的代码中,我有一些不同的结构,所有结构都void(void)存储了大约1000万个成员函数 std::function<void(void)>.

在做std::function<void(void)> func = std::bind(&MyCallBack::Fire, &cb);什么时,有什么方法可以避免内存被动态分配?(或以其他方式将这些成员函数分配给a std::function)

Ser*_*eyA 20

不幸的是,分配器std::function已经被C++放弃了17.

现在,避免内部动态分配的公认解决方案std::function是使用lambdas而不是std::bind.这确实有效,至少在GCC中 - 它有足够的静态空间来存储lambda,但没有足够的空间来存储binder对象.

std::function<void()> func = [&cb]{ cb.Fire(); };
    // sizeof lambda is sizeof(MyCallBack*), which is small enough
Run Code Online (Sandbox Code Playgroud)

作为一般规则,对于大多数实现,并且使用仅捕获单个指针(或引用)的lambda,您将std::function使用此技术避免内部动态分配(通常也是更好的方法,如其他答案所示).

请记住,为了工作,你需要保证这个lambda将比这更长寿std::function.显然,它并不总是可能的,有时你必须通过(大)副本捕获状态.如果出现这种情况,目前还没有办法消除功能动态分配,比用STL自己鼓捣其他(显然,不是一般情况下推荐,但在某些特定情况下可以做).

  • 这是因为`std :: function`有一个优化,可以在堆栈中的对象内部分配内存,前提是函数对象的大小足够小.在这里使用`lambda`将导致一个对象的大小为一个指针,*应该*触发小函数优化并在`std :: function`中分配它 (9认同)
  • @AnT,嗯...我的答案明确规定了OP如何实现他们的目标(获得一个不在GCC上分配的`std :: function <>`)以及为什么没有其他办法.那有什么不相关的? (3认同)
  • `function`在C++ 17中删除分配器"支持"并不是"不幸"; 这是非常幸运的,因为它可以使任何人免于尝试使用它并发现没有图书馆供应商曾经实现它(因为这是不可能的).有点像C++ 11如何为"export"模板删除"支持".:) (3认同)
  • OP也可能对`std :: function`的第三方替换感兴趣,其内存使用可以得到保证(而不是在libc ++和libstdc ++之间的阈值上动态分配).要搜索的关键字是`inplace_function`,如`sg14 :: inplace_function <void(),24>`.然后,如果你发现你需要存储一个更大的lambda并且仍然不想堆分配,你可以将该模板参数从"24"或"40"或其他任何东西中删除,然后重新编译. (3认同)
  • 我不确定这个答案的相关性.是的,分配器支持已被删除.但这并不意味着禁止`std :: function`使用默认机制动态分配内存."删除分配器支持"只是意味着您将无法自定义分配器.那么,这个答案与这个问题有什么关系呢? (2认同)
  • @Quuxplusone这种可调用的真正好处是,与堆栈存储字符串/向量等不同,当你在容器中存储太大的东西时,很容易在编译时捕获. (2认同)
  • @Quuxplusone:等一下,但是OP甚至说*“此代码仅用于说明问题。” * ...显然不是他的实际代码。这个答案通常是说*“现在,为了避免在std :: function内部进行动态分配而采用的解决方案是使用lambda而不是std :: bind。这确实有效,至少在GCC中-它有足够的静态空间来存储lambda,但没有足够的空间来存储活页夹对象。“ *没有提供任何暗示,这仅适用于不带闭包的lambda。我自己没有任何问题,我想按原样说这个答案是虚假的,广泛的主张。 (2认同)

Nir*_*man 5

作为已经存在且正确答案的补充,请考虑以下事项:

MyCallBack cb;
std::cerr << sizeof(std::bind(&MyCallBack::Fire, &cb)) << "\n";
auto a = [&] { cb.Fire(); };
std::cerr << sizeof(a);
Run Code Online (Sandbox Code Playgroud)

这个程序为我打印24和8,包括gcc和clang.我不知道bind这里做了什么(我的理解是它是一个非常复杂的野兽),但正如你所看到的,与lambda相比,这里几乎是荒谬的低效率.

实际上,std::function如果从函数指针构造,则保证不分配,函数指针也是一个单词的大小.因此,std::function从这种lambda中构造一个只需要捕获指向对象的指针并且也应该是一个单词的lambda,实际上应该永远不会分配.

  • 我希望`bind`存储指向成员的指针--16个字节+指向对象的指针--8个字节.你有24岁. (4认同)
  • 我认为 lambda 对象与函数指针具有相同大小这一事实并不相关。如果您从指针构造 std::function,那么它只需要 8 个字节的内部状态。如果您从 lambda 创建它,则该对象必须存储 lambda 的副本和函数指针(由于类型 Erasure)。话虽如此,您所说的大多数实现不会为如此小的对象进行分配的说法是正确的。 (2认同)