使用std::function将类成员函数指针转换为普通函数指针

本文最后更新于:2022年10月18日 下午

起因

因为强迫症 舍曲西林救不了 C++,想把 GLFW 绑定的回调函数也放在类里,但是回调函数要求的类型固定了,例如void (*)(GLFWwindow*,int,int),但是如果函数放在类里那么祂的类型大概是void (A::*)(GLFWwindow*,int,int),而且强制类型转换也不行(倒也讲得通),百度之,得重复文章一整屏,但是大概思路就是用标准库的 std::function 和 std::bind 把这个类函数和实例捏到一起再转换成普通的函数指针,这个操作也和使用某些标准库设施类似(如 std::thread,但仅是使用上,标准库那个实现更加暴力)。

操作

总之就是没看懂标准库的神奇操作,转为使用标准库工具来搞。

std::function有个target函数得到任意类型的函数指针,然后问题就是怎么能把对象和成员函数绑到一起变成一个普通的函数。

Google 之得到可使用std::bind这个工具,祂可以把可调用的东西进行转换,比如调换传参顺序、增减参数等(有点柯里化的味),也可以实现我需要的操作。

最后得到这么个阴间东西:

1
std::function<void(GLFWwindow*, int, int)>(std::bind(&MainWindow::Resize, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)).target<void(GLFWwindow*, int, int)>()

拆开看就是用bind绑定函数并用其构造function,再使用function进行类型转换。

通用写法例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A
{
void func(int a)
{
(void)a;
}

A()
{
auto f = std::bind(&A::func, this, std::placeholders::_1);
std::function<void(int)> fun(f);
void (*pfun)(int) = fun.target<void(int)>();
}
};

这下就是实现了预定目标,不过写这代码容易被打,还是用原方案吧!这波啊是用强迫症对付强迫症

P.S. 说起本质,其实是想让这个函数读取类内的成员更方便,那为啥不用友元呢?

标准库的操作

标准库的实现(MSVC)如下(TL;DR 可直接看操作):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    template <class _Tuple, size_t... _Indices>
static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ {
// adapt invoke of user's callable object to _beginthreadex's thread procedure
const unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));
_Tuple& _Tup = *_FnVals;
_STD invoke(_STD move(_STD get<_Indices>(_Tup))...);
_Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABI
return 0;
}

template <class _Tuple, size_t... _Indices>
_NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {
return &_Invoke<_Tuple, _Indices...>;
}

template <class _Fn, class... _Args>
void _Start(_Fn&& _Fx, _Args&&... _Ax) {
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
//构造线程标识符(_Thr)里的句柄
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
//这里大概是调用,使用上面的_Get_invoke -> _Invoke -> invoke
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});

#pragma warning(push)
#pragma warning(disable : 5039) // pointer or reference to potentially throwing function passed to
// extern C function under -EHc. Undefined behavior may occur
// if this function throws an exception. (/Wall)
_Thr._Hnd =
reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
#pragma warning(pop)

if (_Thr._Hnd) { // ownership transferred to the thread
(void) _Decay_copied.release();
} else { // failed to start thread
_Thr._Id = 0;
_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
}
}

public:
//构造函数 传入函数、成员函数的对象、参数等
template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
_NODISCARD_CTOR explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
}

这波强转类型给我看傻了,其中一些定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using _Thrd_id_t = unsigned int;
struct _Thrd_t { // thread identifier for Win32
void* _Hnd; // Win32 HANDLE
_Thrd_id_t _Id;
};
typedef void (__cdecl* _beginthread_proc_type )(void*);
typedef unsigned (__stdcall* _beginthreadex_proc_type)(void*);

_ACRTIMP uintptr_t __cdecl _beginthreadex(
_In_opt_ void* _Security,
_In_ unsigned _StackSize,
_In_ _beginthreadex_proc_type _StartAddress,
_In_opt_ void* _ArgList,
_In_ unsigned _InitFlag,
_Out_opt_ unsigned* _ThrdAddr
);

大概就是把函数首地址、参数列表全部放到一个结构体里,至于怎么调用的,虽然找到的调用位置,但是不知道为啥貌似没把函数传进去,希望有大佬给我讲讲 orz。

// TODO 有生之年再加点 2022/4/3 02:52


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!