如何实现std :: function? [英] How is std::function implemented?

查看:348
本文介绍了如何实现std :: function?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据我发现的源,一个lambda表达式基本上是由编译器创建一个具有重载的函数调用操作符和被引用的变量作为成员的类实现的。这表明lambda表达式的大小不同,并且给定足够的引用变量,该大小可以是任意大的。



std :: function 应具有固定大小,但它必须能够包装任何类型的可调用项,包括任何同类型的lambda。如何实现?如果 std :: function 在内部使用指向其目标的指针,那么当 std :: function 实例是复制还是移动?是否有任何堆分配?

解决方案

std :: function 可以从一个实现到另一个不同,但核心思想是它使用类型擦除。虽然有多种方法可以做,你可以想象一个平凡的(不是最优的)解决方案可能是这样的(简化为 std :: function< int(double)> 为了简单起见):

  struct callable_base {
virtual int operator )= 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 存储到基本类型。对于与函数一起使用的每个不同函子,将创建从基本派生的新类型,并动态实例化该类型的对象。 std :: function 对象总是具有相同的大小,并将根据堆中不同的函子的需要分配空间。



在现实生活中,有不同的优化,提供性能优势,但复杂的答案。类型可以使用小对象优化,动态分派可以由一个自由函数指针替换,它接受函子作为参数,以避免一个间接层次...但是想法基本上是一样的。






关于 std :: function 行为的副本问题,

  // g ++ 4.8 
int main(){
int value = 5;
typedef std :: function< void()>有趣
fun f1 = [=]()mutable {std :: cout<<值++< '\\\
'};
fun f2 = f1;
f1(); // prints 5
fun f3 = f1;
f2(); // prints 5
f3(); //打印6(第一次递增后复制)
}

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


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.

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?

解决方案

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); }
// ...
};

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.


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++ << '\n' };
   fun f2 = f1;
   f1();                    // prints 5
   fun f3 = f1;
   f2();                    // prints 5
   f3();                    // prints 6 (copy after first increment)
}

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.

这篇关于如何实现std :: function?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆