VC++中的std::function比较问题

多年前VC++刚完善C++11那会儿,我就已经考虑将一个包含有unique timer模型的库中函数指针替换成std::function了,当时就苦恼std::function要如何比较,满世界的论坛,博客找了个遍,也没有答案,最终我只能去二进制层面寻求方法。

最初的时候,我一直在用一段代码来处理这个问题:

template
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) {
		return (pa->vt[2] == pb->vt[2]);
	}
	return false;
}

// 请注意,这段代码有bug

在VC++中,可以使用这个结构来模拟std::function的二进制数据:

struct __std_func_struct {

    size_t* vt; // 虚函数表

    void* func_ptr; // 函数指针

};

虚函数表中储存的函数地址:

vt[0] 与 vt[1] 相等,这两个虚函数不重要,不需要我们关注。

vt[2] 中保存了 std::_Func_impl_no_alloc<模板参数...>::_Do_call 地址

_Do_call 这个地址是我们比较两个std::function的关键信息。

保存 函数指针 的 std::function对象:

        函数指针 会被 保存到 func_ptr 中。

        std::_Func_impl_no_alloc<模板参数...>::_Do_call() 中通过 jmp [*cx + 8] 来跳转

保存 函数对象 的 std::function对象:

        函数对象 又有两种情况:

            inline 时:代码直接内联编译至 _Do_call()

            noinline时:在 _Do_call() 中 会静态跳转到一个函数地址。

        总之 函数对象 绝对不涉及到func_ptr

来自std::bind的目标依旧会保持上面两个原则。

在大多数情况下,我在本文开头给出的那段代码是没有问题的,直到有一天我写出了bug,经过长时间还原bug后,最终我发现:

在一个std::function保存 函数对象 时,func_ptr不会被初始化,这导致func_ptr的判断本身就是不准确的。

由此,我意识到得判断是否存在函数对象,于是我想到了可以从std::function::target_type().name()信息中查找是否包含

// 请注意,这段代码仅适用于VC++,我不确定G++是否是相同的结构
// 并且我不保证随着编译器更新是否会有二进制差异

template
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不相等,我们甭管它保存了什么,都先去类型信息中先查找vt[2] == pb->vt[2]);
		}
		else {
			// 两个都是函数指针时,比较func_ptr
			return (pa->func_ptr == pb->func_ptr);
		}
	}
}

你可能感兴趣的:(C++,c++,开发语言)