std::function 是如何实现的?

2021-12-23 00:00:00 lambda c++ c++11

根据我找到的资料,lambda 表达式 本质上是由编译器实现的,该类使用重载的函数调用运算符和引用的变量作为成员.这表明 lambda 表达式的大小各不相同,并且给定足够的引用变量,大小可以任意大.

According to the sources I have found, a lambda expression is essentially implemented by the compiler creating a class with overloaded function call operator and the referenced variables as members. This suggests that the size of lambda expressions varies, and given enough references variables that size can be arbitrarily large.

std::function 应该具有固定大小,但它必须能够包装任何类型的可调用对象,包括任何同类的 lambda.它是如何实施的?如果 std::function 在内部使用指向其目标的指针,那么当 std::function 实例被复制或移动时会发生什么?是否涉及任何堆分配?

An std::function should have a fixed size, but it must be able to wrap any kind of callables, including any lambdas of the same kind. How is it implemented? If std::function internally uses a pointer to its target, then what happens, when the std::function instance is copied or moved? Are there any heap allocations involved?

推荐答案

std::function 的实现可以从一个实现到另一个不同,但核心思想是它使用类型擦除.虽然有多种方法可以做到这一点,但您可以想象一个简单的(不是最佳的)解决方案可能是这样的(针对 std::function 的特定情况进行了简化)为了简单起见):

The implementation of std::function can differ from one implementation to another, but the core idea is that it uses type-erasure. While there are multiple ways of doing it, you can imagine a trivial (not optimal) solution could be like this (simplified for the specific case of std::function<int (double)> for the sake of simplicity):

struct callable_base {
   virtual int operator()(double d) = 0;
   virtual ~callable_base() {}
};
template <typename F>
struct callable : callable_base {
   F functor;
   callable(F functor) : functor(functor) {}
   virtual int operator()(double d) { return functor(d); }
};
class function_int_double {
   std::unique_ptr<callable_base> c;
public:
   template <typename F>
   function(F f) {
      c.reset(new callable<F>(f));
   }
   int operator()(double d) { return c(d); }
// ...
};

在这个简单的方法中,function 对象将只存储一个 unique_ptr 到一个基本类型.对于与 function 一起使用的每个不同的函子,会创建一个从基派生的新类型,并动态实例化该类型的对象.std::function 对象始终具有相同的大小,并将根据需要为堆中的不同函子分配空间.

In this simple approach the function object would store just a unique_ptr to a base type. For each different functor used with the function, a new type derived from the base is created and an object of that type instantiated dynamically. The std::function object is always of the same size and will allocate space as needed for the different functors in the heap.

在现实生活中,有不同的优化可以提供性能优势,但会使答案复杂化.类型可以使用小对象优化,动态分派可以用一个自由函数指针代替,该指针以函子为参数以避免一级间接......但想法基本相同.

In real life there are different optimizations that provide performance advantages but would complicate the answer. The type could use small object optimizations, the dynamic dispatch can be replaced by a free-function pointer that takes the functor as argument to avoid one level of indirection... but the idea is basically the same.

关于 std::function 副本的行为问题,快速测试表明内部可调用对象的副本已完成,而不是共享状态.

Regarding the issue of how copies of the std::function behave, a quick test indicates that copies of the internal callable object are done, rather than sharing the state.

// g++4.8
int main() {
   int value = 5;
   typedef std::function<void()> fun;
   fun f1 = [=]() mutable { std::cout << value++ << '
' };
   fun f2 = f1;
   f1();                    // prints 5
   fun f3 = f1;
   f2();                    // prints 5
   f3();                    // prints 6 (copy after first increment)
}

测试表明 f2 获取可调用实体的副本,而不是引用.如果可调用实体由不同的 std::function<> 对象共享,则程序的输出将是 5、6、7.

The test indicates that f2 gets a copy of the callable entity, rather than a reference. If the callable entity was shared by the different std::function<> objects, the output of the program would have been 5, 6, 7.

相关文章