C++的std::function

从函数指针到std::function

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

void func()
{
	std::cout << "普通函数";
}
class T
{
public:
	void operator()()
	{
		std::cout << "仿函数";
	}
};

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

void (*p1)(void) = func;
p1();

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

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

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

使用std::function

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

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

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

std::function<void(void)> f1(func),f2(T());
f1();
f2();

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

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

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

std::function与lambda

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

std::function的缺点

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

#include <iostream>
#include<functional>
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;
}

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

std::function的可读性

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

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

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

typedef void(*(*T)(void))(void);

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

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

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

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

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

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值