• C++的std::function


    函数指针到std::function

    函数指针的作用大家应该十分熟悉,它使得我们可以把函数当成参数传递。在C语言中,这种方法几乎非常完美,可以实现基本上所有的传递函数的操作。但是在C++中,由于仿函数的出现,C语言中的函数指针变得不通用了。(仿函数就是重载了operator()的类。)

    void func()
    {
    	std::cout << "普通函数";
    }
    class T
    {
    public:
    	void operator()()
    	{
    		std::cout << "仿函数";
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    想要绑定func函数,非常简单,只需要使用以下代码即可:

    void (*p1)(void) = func;
    p1();
    
    • 1
    • 2

    然而,同样的方法并不能绑定仿函数!因为在C++中,仿函数可能读取成员变量,所以被视为与普通函数不同的类型。要想绑定仿函数,我们必须这样:

    void (T:: * p2)(void) = &T::operator();
    (T().*p2)();
    
    • 1
    • 2

    这样一来,对于仿函数的操作就麻烦了许多,所以,我们必须寻求一种通用的方法。这个方法就是std::function。

    使用std::function

    std::function是C++标准库中的一个模板类,用来保存一个函数、仿函数或lambda表达式。std::function重载了operator(),使得调用函数就是普通的方式。使用前需要包含头文件。使用格式是:

    std::function<返回值类型(参数类型)> 对象名(函数、仿函数或lambda表达式);
    
    • 1

    例如上面的例子就可以写成:

    std::function<void(void)> f1(func),f2(T());
    f1();
    f2();
    
    • 1
    • 2
    • 3

    值得注意的是,std::function的模板参数不能省略参数类型外面的括号,即使没有参数也不能。例如,上例就不能写成

    std::function<void> f1(func);//会报错
    
    • 1

    另外,模板参数也不能指定noexcept。例如std::function也是不行的。但这并不意味着不能绑定noexcept函数,如果想绑定noexcept函数直接用std::function即可。

    std::function与lambda

    值得注意的是,lambda表达式的类型并不是std::function,而是一种编译器自动生成的类,与std::function类似,lambda表达式也重载了operator(),但它们的类型和std::function并不一样。为什么std::function能存储lambda表达式呢?因为lambda表达式能够隐式转换为函数指针,这样就可以再转为std::function了。

    std::function的缺点

    std::function虽然很好用,但是也有一点缺陷,使其不能完全代替函数指针。这个缺陷就是,因为它可以绑定仿函数,所以它不能作为编译期常量。如下面的例子:

    #include 
    #include
    void func()
    {
    	std::cout << "test";
    }
    consteval std::function<void(void)> GetFunc1()
    {
    	return func;
    }
    using FuncType = void(*)(void);
    consteval FuncType GetFunc2()
    {
    	return func;
    }
    int main()
    {
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    函数GetFunc1会报错,因为std::function不能作为编译期常量。
    报错
    正因为如此,std::function不能作为模板的非类型参数的类型,但是函数指针可以。

    std::function的可读性

    如果在使用函数指针和std::function都行的情况下,应该优先选用哪个呢?我个人的建议是优先使用std::function,因为他比起函数指针来可读性大大增加(C++函数指针的语法特别变态,特别是复杂了的话就非常难懂)。比如下面这个例子:

    using T = void(*(*)(void))(void);
    
    • 1

    你认为T是什么类型?相信大多数人肯定一脸懵逼。其实T是一个函数指针,指向的函数A返回值又是一个函数指针,指向函数B,A无参数,B的返回值是void,B无参数。
    再拿这个例子,如果把它转成typedef,应该把类型名称放哪里?这又是个很麻烦的问题,其实,转成typedef是这样的:

    typedef void(*(*T)(void))(void);
    
    • 1

    试问这样的代码是给人看的?对于这种问题,有两种解决方法,一是及时使用using来提高可读性,如:

    using T1 = void(*)(void);//函数指针,无参无返回值,相信这个大家还能看懂
    using T = T1(*)(void);//函数指针,无参,返回值类型是T1
    
    • 1
    • 2

    C++的函数指针变态就变态在星号和参数列表等位置特别反人类。具体来说,上面这个例子这样可以编译通过,但是如果直接把T1带入到T中求出Td的完整形式,那对不起,你想得太简单了,根本编译通不过!这就是函数指针的变态之处。而这样把T1看成一个整体,就提高了可读性。
    还有一种办法,就是使用std::function。使用std::function的代码:

    using T = std::function<std::function<void(void)>(void)>;
    
    • 1

    这样是不是特别清晰明了?这个函数返回值是std::function,无参数,一眼就能看出来,也符合人类的习惯。所以,在函数指针和std::function都可以使用的情况下,推荐使用后者。

  • 相关阅读:
    python java php村委会管理系统vue+elementui
    打开google search,从taskbar拖拽全屏应用比如Google进入分屏,页面出现Launcher报错
    php实战案例记录(25)intval函数的用法
    【AIGC】Stable Diffusion Prompt 每日一练0915:机车女孩
    超级账本Fabric的世界状态操作与账本操作
    chattr:修改文件的特殊属性
    外卖项目03---分类管理业务开发
    【Spring进阶系列丨第二篇】Spring中的两大核心技术IoC(控制反转)与DI(依赖注入)
    【JUC】七、读写锁
    c语言小项目(三子棋游戏实现)
  • 原文地址:https://blog.csdn.net/qq_54121864/article/details/126532536