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的所有权只在函数里面。

https://github.com/Mryange/cpp-template-tools

全部评论

相关推荐

评论
4
6
分享

创作者周榜

更多
牛客网
牛客企业服务