IE盒子

搜索
查看: 118|回复: 0

C++17 std::invoke

[复制链接]

3

主题

8

帖子

16

积分

新手上路

Rank: 1

积分
16
发表于 2023-1-12 09:38:27 | 显示全部楼层 |阅读模式
std::invoke

std::invoke 可以调用任何类型的可调用对象,包括常规函数指针和成员函数指针
今天我们要讨论的是C++17 新推出的功能 - std::invoke。首先,我们先看看官网让如何描述的


这些文档看起来相当复杂,并且有很多东西。所以我想回顾一个这个。我们得到的是一个函数,它为我们提供了一个统一的接口来调用可调用的东西,假如我们有一个名为"do_something"的函数,它会只返回一个值
#include <iostream>
#include <functional>

int do_something(const int i){
    return 5 + i;
}

int main(){
    std::cout << "\n";
    std::cout << std::invoke(&do_something, 5) << std::endl;
    return 0;
}
构建获得了我们预期的值:10


也许这个不是世界上最神奇的事,但是通过为我们提供调用方法和函数以及任何可调用的东西的统一接口,我们获得的是不必知道在C++ 世界中被认为相当深奥的东西,例如如何调用成员函数指针。因此,为了举例说明,我们将创建一个结构并为其提供一个方法。
创建一个结构s并且我们可以调用s.do_something(3),那么我们希望它返回:8
#include <iostream>
#include <functional>

int do_something(const int i){
    return 5 + i;
}

struct S
{
    int j = 5;

    int do_something(const int i){
        return j + i;
    }
};

int main(){
    std::cout << "\n";
    std::cout << std::invoke(&do_something, 5) << std::endl;

    S s;
    std::cout << s.do_something(3) << std::endl;
    return 0;
}


如果我们现在出于某种原因想要使用成员函数指针,并且在模板编程和C++中有很多原因,但我会尽量让这个示例尽可能简单。
所以我们得到了一些东西-假设我们要执行“自动函数指针等于s.do_something”让我们确保即使这样也能编译
如果想调用这个函数指针“fp”,那么我们必须执行“s.fp”,然后将其传递给它;
auto fp = &S::do_something;
我很确定我是对的,事实上,让我们给它一个不同的值,这样我们就知道我们在哪里了
std::cout << (s.*fp)(2) << std::endl;
在这里我要纠正S的实际类型是 int S::*int; 我们称之为fp2 ,等于 S::do_something。我们将对此进行第二次尝试,并确保我们的所有语法仍然正确,而且我们做到了
auto fp = &S::do_something;
int (S::*fp2)(int) = &S::do_something;

std::cout << (s.*fp)(2) << std::endl;
std::cout << (s.*fp2)(1) << std::endl;


假设有第二个具有完全相同签名的成员函数,我们将其称为do_something2,我们不是将它们相加,而是将它们相乘,现在这里可能有理由在我们的块的某些开关中,如“如果为真,fp2 = S::dosomething2”否则使其等于“do_something”,仍然让所有语法正确。所以我们现在在这里第二次调用时,我们得到的结果基本上是5的1倍,因为j已经在上面初始化为5,而且我们正在调用do_something2,这就是为什么我们将5作为最后一个结果打印出来的原因。所以这一切都是复杂的。
#include <iostream>
#include <functional>

int do_something(const int i){
    return 5 + i;
}

struct S
{
    int j = 5;

    int do_something(const int i){
        return j + i;
    }

    int do_something2(const int i){
        return j * i;
    }
};

int main(){
    std::cout << "\n";
    std::cout << std::invoke(&do_something, 5) << std::endl;

    S s;
    std::cout << s.do_something(3) << std::endl;

    auto fp = &S::do_something;
    int (S::*fp2)(int) = nullptr;

    if(true){
        fp2 = &S::do_something2;
    }else{
        fp2 = &S::do_something;
    }

    std::cout << (s.*fp)(2) << std::endl;
    std::cout << (s.*fp2)(1) << std::endl;
    return 0;
}
如果我们想使用一堆不同的类型和某种模板函数,它就会开始失控。所以我们可以做
std::invoke(&dosomething,s,2)
因此,这为我们提供了一个通用接口,可以使用一组参数调用任何可调用的东西。这也太酷了,如果你为任何类型的函数指针传递,或成员函数指针传递,或任何类型的可调用的模板编程,这可能真的会为你节省很多工作,您需要实现的重载次数,具体取决于您得到的是const 事物、volatile事物、自由函数、成员函数、成员数据等。
我们不是调用do_something,而是说 j of s。所以我们甚至可以使用它来访问成员数据。
std::invoke(&S::j, s);
与 std::bind 或 s.do_something(5) 等替代方法相比,使用 std::invoke 有哪些优缺点?

std::invoke 可以调用任何类型的可调用对象,包括常规函数指针和成员函数指针。当您不知道自己拥有哪个时,例如在模板代码中,请使用它。std::bind 更重量级;我以前没有亲自使用过它,但我认为目标是将 fp/member fp 更改为采用不同数量参数的不同可调用对象。如果你只需要调用一个函数指针而不需要花哨的 std::bind 功能,使用s.do_something(5) 不太灵活,但显然更具可读性(并且可能更快)。该语法不允许调用函数指针;相关的 (s.*fp)(5) 允许调用成员函数指针但不允许调用常规函数指针。
std::invoke 和 std::function 的区别:

std::function 适用于需要实际保留可调用对象以便稍后使用它的情况。std::invoke 用于当您需要立即使用该可调用对象而不存储它以备后用时使用。它们都抽象了调用语法,但是以不同的方式进行。std::function 更重量级,因为它需要一种方法来统一存储所有这些不同类型,而 std::invoke 如果使用得当则没有开销,因为它只是根据可调用对象的类型选择语法。
参考自:
官网[1]  、Json Turner 的视频讲解
参考


  • ^https://en.cppreference.com/w/cpp/utility/functional/invoke
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表