在VC++中,一个std::function对象会根据情况被编译成多种情况,函数指针,函数对象(lambda),其中还涉及了优化等问题。
对于函数对象来说,常规的手段没有任何办法可以做比较,所以只能去二进制层面看看底层数据。
在VC中可以用同一个结构去模拟std::function对象
struct std_function_struct{
// vtable[0] 与 vtable[1] 是相同的地址,其中是std::_Func_impl_no_alloc<模板参数...>::_Move地址 或者 std::_Func_impl_no_alloc<模板参数...>::_Copy地址
// vtable[2] 中保存了 std::_Func_impl_no_alloc<模板参数...>::_Do_call 地址,这个地址是我们的判断依据。
size_t* vtable;
// 只有函数指针才会保存在这,哪怕lambda不能inline,其地址也会被直接静态编译到_Do_call中
void* func_ptr;
// ... 其他信息对于我们的需求来说并不重要
};
如果是函数指针,很显然的指针就放在 func_ptr 中,比较就它就行。
如果是lambda,我们可以比较vtable[2]是否相同
vtable[2] 中存放的是 std::_Func_impl_no_alloc<模板参数...>::_Do_call() 地址
根据模板参数的不同,哪怕是lambda,std::_Func_impl_no_alloc::_Do_call 也会编译成不同的代码。
其中还涉及了优化问题,两段完全相同代码的lambda,会优化成同一段代码,这不就正好满足了我们需求的判断吗?
但其中有一个比较遗憾的地方,std::function在保存lambda的对象时,func_ptr是不会初始化为0的。
所以我们不能简单的先比较func_ptr再比较vtable[2],我们很大概率是需要先判断两个对象是否存在lambda,好在有std::function::target_type()可以满足我们的要求。
最终代码如下:请注意,这段代码仅适用于VC++,并且不确定随着编译器更新是否能保持二进制一致性。
template<typename _Ty> bool is_std_func_equal(_Ty& a, _Ty&b) { struct __std_func_struct { size_t* vt; void* func_ptr; }; __std_func_struct* pa = (__std_func_struct*)&a; __std_func_struct* pb = (__std_func_struct*)&b; if (pa->func_ptr == pb->func_ptr) { // 如果func_ptr相等时,我们直接判断两个vtable[2]即可 return (pa->vt[2] == pb->vt[2]); } else { // 只要func_ptr不相等,我们甭管它保存了什么,都先去类型信息中先查找<lambda_ bool al = (strstr(a.target_type().name(), "<lambda_") != NULL); bool bl = (strstr(b.target_type().name(), "<lambda_") != NULL); if (al != bl) // 一个是lambda而另一个不是时,就不需要比较了 return false; if (al) { // 两个都是lambda时,比较vtable[2] return (pa->vt[2] == pb->vt[2]); } else { // 两个都是函数指针时,比较func_ptr return (pa->func_ptr == pb->func_ptr); } } }