std::function 学习笔记

隐约知道有个东西叫做std::function,但一直用的习惯的是c 风格函数指针,那么,什么是 std::function呢?这个新东西的应用场景是什么?和函数指针的区别是什么?什么时候该用函数指针,什么时候该用std::function?它潜在的代码的开销是什么?用函数指针初始化,std::function ,会如何呢?

1. std::function的 定义:https://zh.cppreference.com/w/cpp/utility/functional/function

类模板 std::function 是通用多态函数封装器。 std::function 的实例能存储、复制及调用任何可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。---将这么多可以通过 "()"调用的东西统一 起来的东西 就是std::function,它的语法和具体的应用,依赖于模板技术。

存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为。调用 std::function 的目标导致抛出 std::bad_function_call 异常。

std::function 满足可复制构造 (CopyConstructible) 可复制赋值 (CopyAssignable) 

 

2. 应用场景是 什么?

光看定义看不出来啊

3.和 函数指针的区别 是什么 ?

看定义的话,很明显,这个东西,是函数指针的超集

4. 那如何在函数指针和std::function中做选择呢?

先看一个小例子:

假装我们有一个复杂的数据库结构:

enum class FLAG : uint32_t
{
	_FLAG_USER_,
	_FLAG_ADMIN_
};
typedef struct _LITTLE_ITEM_
{
	std::string strTemp1;
	std::string strTemp2;
	std::string strtemp3;
	FLAG		iTargetFlag;
}LITTLE_ITEM,*PLITTLE_ITEM;
const LITTLE_ITEM G_FAKE_DB[] = {
	LITTLE_ITEM{"假装复杂","假装复杂1","假装复杂2",FLAG::_FLAG_USER_},
	LITTLE_ITEM{"假装复杂","假装复杂1","假装复杂2",FLAG::_FLAG_ADMIN_},
	LITTLE_ITEM{"假装复杂","假装复杂1","假装复杂2",FLAG::_FLAG_USER_},
	LITTLE_ITEM{"假装复杂","假装复杂1","假装复杂2",FLAG::_FLAG_USER_}
};

对于一个存储结构,我们平时做的最多的一件事,可能就是遍历了吧,遍历得时候,顺便干这干那。那么我们按照传统的方式尝试写一个通用的遍历函数:

typedef bool(*pfnCallback)(LITTLE_ITEM* data);
void EnumFunc(LITTLE_ITEM* pData, uint32_t u32Cnt, pfnCallback callback)
{
	for (uint32_t i = 0; i < u32Cnt; ++i) {
		if (!callback(pData + i)) {
			break;
		}
	}
}

试着在此基础上,实现遍历输出:


bool printfItem(const LITTLE_ITEM* data)
{
	std::cout << "[" << data->strTemp1 << "," << data->strTemp2 << "," << data->strtemp3 << "," << static_cast<int>(data->flag) << "]" << std::endl;
	return true;
}
void EnumAndOutPut()
{
	EnumFunc(G_FAKE_DB, _countof(G_FAKE_DB), printfItem);
}

int main()
{
	EnumAndOutPut();
	return 0;
}

但我们通常不仅仅需要输出,还需要进行一些统计信息,比如,要统计flag 为 _FLAG_USER_的个数。

既然要统计个数,就需要个位置存储这个计数器,当前接口不变的话,很难搞,只能用全局变量做累计了,单线程尚且能满足需求,尽管在调用前后需要考虑初始化,维护起来及其难受,加上多线程的话,根本没法用,所以肯定得改接口。

加一个,context 吧,编程中到处可见的姿势:

typedef bool(*pfnCallbackWithContext)(void* context,const LITTLE_ITEM* data);
void EnumFuncWithContext(const LITTLE_ITEM* pData, const uint32_t u32Cnt, void* context, pfnCallbackWithContext callback)
{
	for (uint32_t i = 0; i < u32Cnt; ++i) {
		if (!callback(context,pData + i)) {
			break;
		}
	}
}
bool printfItemAndCnt(void* context ,const LITTLE_ITEM* data)
{
	int* cnt = static_cast<int*>(context);
	if(data->flag == FLAG::_FLAG_USER_)
		++(*cnt);
	std::cout << "[" << data->strTemp1 << "," << data->strTemp2 << "," << data->strtemp3 << "," << static_cast<int>(data->flag) << "]" << std::endl;
	return true;
}
void EnumAndOutPutAndCntUserCnt()
{
	int cnt = 0;
	EnumFuncWithContext(G_FAKE_DB, _countof(G_FAKE_DB), static_cast<void*>(&cnt), printfItemAndCnt);
	std::cout << "context cnt[" << cnt << "]" << std::endl;
}
int main()
{
	EnumAndOutPutAndCntUserCnt();
	return 0;
}

 

 

但我们通常不仅仅需要输出,还需要进行一些统计信息,比如,要分别统计flag 为 _FLAG_USER_ 的个数以及flag 为_FLAG_ADMIN 的个数,此时,简单修改,依然能满足需求:

typedef struct _CNT_ADMIN_AND_USER_
{
	int CntAdmin = 0;
	int CntUser = 0;
}CNT_ADMIN_AND_USER,*PCNT_ADMIN_AND_USER;
bool printfItemAndCntAdminAndUser(void* context, const LITTLE_ITEM* data)
{
	auto cnt = static_cast<PCNT_ADMIN_AND_USER>(context);
	if (data->flag == FLAG::_FLAG_USER_)
		++cnt->CntUser;
	else if (data->flag == FLAG::_FLAG_ADMIN_)
		++cnt->CntAdmin;
	std::cout << "[" << data->strTemp1 << "," << data->strTemp2 << "," << data->strtemp3 << "," << static_cast<int>(data->flag) << "]" << std::endl;
	return true;
}
void EnumAndOutPutAndCntUserCntAdminCnt()
{
	CNT_ADMIN_AND_USER cnt;
	EnumFuncWithContext(G_FAKE_DB, _countof(G_FAKE_DB), static_cast<void*>(&cnt), printfItemAndCntAdminAndUser);
	std::cout << "context cnt admin[" << cnt.CntAdmin << "] cnt user[" << cnt.CntUser << "]" << std::endl;
}
int main()
{
	EnumAndOutPutAndCntUserCntAdminCnt();
	return 0;
}

但我们通常不仅仅需要输出,还需要进行一些统计信息,比如,要分别统计flag 为 _FLAG_USER_ 的个数以及flag 为_FLAG_ADMIN 的个数,然后 产品经理说,要不也统一一下strTemp1 的个数吧,原地爆炸

以上代码,需求频繁变更的时候,自然有不变的地方,即:EnumFuncWithContext 所需要的参数类型是不变的,这部分,看来抽象的比较充分了,可以满足各种类型的需求,这里的context的概念,极其通用,通过其类型void* 就可以看出来,以不变应万变。

但是,这一个优秀的地方,依然抵不住频繁的变更。那我们应该如何做呢?

观察上面的代码,可以明显的感觉到,这个枚举回调操作本身是有个模式的,枚举遍历动作被抽象出来了,然后带着某些context 执行一些动作。c++ 里面,很容易想到类的概念,context,可以视为数据,动作就是类函;但其实这里,context 有着更广阔的含义,它的含义,就是context 本身的概念,即上下文,或者说类似于c++ 中的作用域,遍历枚举函数的回调函数需要带着这样的一个上下文去做一些动作,可能是要改变某些对象的成员,可能是要改变某个局部变量的值,可能是要改变某个全局变量的值;一切,在调用这个函数的时候,可见的,作用域内部的东西,都可以被视为一个上下文被传进来。

 

而std::function,这样一个以模板类存在的东西,给了我们一种,及其方便的,通用 的,带着context 执行可调用函数的方法。

下一篇继续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值