C++模板学习——实现function_ref
在很多的教程中,通常都是把std::function 当作一种高级的函数指针。
但是实际上,function是函数的一份拷贝。
严格鸽:现代C++学习——实现一个std::function
你可以发现,如果是一个lambda/仿函数之类的,带有状态的“函数”, function就会copy一份。
我们可以理解为,function 复制了一份资源。
如果你了解C++17,就会知道有一个工具类叫做string_view ,它不持有字符串的所有权,只是借用了一下(所以它叫过视图)
那么是否可以实现一个函数的借用,也就是function_ref。
先看一下使用
int sum(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } void foo(Function_ref<int(int, int)> fn_ref) { std::cout << fn_ref(3, 5) << "\n"; /* do some thing */ } int main() { foo(sum); foo(mul); int cnt = 0; foo([&](int, int) {cnt++;return 114514; }); std::cout << "cnt :" << cnt << "\n"; }
输出
8
15
114514
cnt :1
首先的想法是,函数指针。
using func_type = int(*)(int,int); void foo(func_type fn_ref) { std::cout << fn_ref(3, 5) << "\n"; /* do some thing */ } int main() { foo(sum); foo(mul); }
这样对于普通函数是可以的,但是对于lambda这种还有其他状态的函数来说,是不能转成函数指针的。
为什么东西可以“接住”任何一种类型呢?
std::any 可以,但是要知道,function 和any的实现是差不多的(都要有虚函数调用(不考虑小对象优化))。
还有一个,void*
int main() { void *fnptr = reinterpret_cast<void *>(&mul); fnptr = reinterpret_cast<void *>(&sum); int cnt{0}; auto f = [&](int a, int b) {cnt++;return a + b; }; fnptr = reinterpret_cast<void *>(&f); }
emmmm,倒是接住了,所以如何调用呢?
我们还需要提供一个类型信息,是的void*可以获得真实的函数类型。
template <typename Fn> void foo(void *fnptr) { auto real_fn = *reinterpret_cast<Fn *>(fnptr); std::cout << real_fn(3, 5) << "\n"; } int main() { void *fnptr = reinterpret_cast<void *>(&mul); foo<decltype(mul)>(fnptr); fnptr = reinterpret_cast<void *>(&sum); foo<decltype(sum)>(fnptr); int cnt{0}; auto f = [&](int a, int b) {cnt++;return 114514; }; fnptr = reinterpret_cast<void *>(&f); foo<decltype(f)>(fnptr); std::cout << "cnt :" << cnt << "\n"; }
所以,我们可以在一个类里面,维护一个void*来指向函数,在额外维护一个类型信息。
但是如果保存类型信息呢?看一下实现代码就可以了
template<typename T> struct Function_ref; template<typename Ret,typename ...Args> struct Function_ref<Ret(Args...)>{ using Type = Ret(*)(void * fnptr,Args...args); void * fnptr{nullptr}; Type callfn_withtype; template<typename Fn> static Ret call_fn(void *fnptr,Args ... args){ auto real_fn = *reinterpret_cast<Fn*>(fnptr); return real_fn(std::forward<Args>(args)...); } Function_ref() = default; Function_ref(std::nullptr_t){}; template<typename Func> Function_ref(Func && func){ callfn_withtype = call_fn<std::remove_reference_t<Func>>; fnptr = reinterpret_cast<void*>(&func); } Ret operator()(Args ... args) const { return callfn_withtype(fnptr,std::forward<Args>(args)...); }; operator bool () const {return fnptr;} };
我们不能单纯的维护一个类型信息(毕竟是静态类型语言),但是我们可以维护一个指向[携带了类型的函数]的指针。
当然因为这个ref只是借用了所有权,也就是对于这样的函数,实现了一个回调。
void foo(int n, Function<void()>& call_back) { int sum = 0; for (int i = 1; i <= n; i++)sum += i; call_back = [=]() { cout << "sum from 1"<<" to "<<n <<" is " << sum << endl; }; }
这个是不能的,因为lambda的所有权只在函数里面。