存储指向成员函数的指针不适用于 CLak++

storing pointer to member function does not work for clang++

本文关键字:指针 不适用 适用于 CLak++ 函数 成员 存储      更新时间:2023-10-16

当我使用 clang++ 时,我只能调用指向成员的指针函数,我无法强制转换它或将其分配给变量以在需要时调用 i(我想将此变量存储到函数数组中),但使用 G++ 我可以做到这一点,

例如
class Base {
public:
typedef void (Base::*A)();
virtual void some_func() = 0;
};
class B: public Base {
public:
void some_func() {
return
}
};
int main() {
B b;
auto h = (Base::A)&Base::some_func;
typedef void (*my_function)();
auto some_func = (my_function)(b.*h);
some_func();
return 0;
}

使用G++ 可以编译和运行,但使用 CLANG++,我得到reference to non-static member function must be called; did you mean to call it with no arguments?(请注意,我不能在我的代码中使用任何 std::x 函数,因为代码在裸机上运行

你在 g++ 中误用了绑定成员函数扩展。您正在强制转换为错误类型的函数指针,该指针具有未定义的行为:

typedef void (*my_function)();
auto some_func = (my_function)(b.*h);
some_func();

(my_function)(b.*h)表达式解析虚拟调度以确定(b.*h)()将调用哪个覆盖函数,但要通过返回的指针实际调用该函数,您仍然需要提供指向对象的this指针。所以my_function应该接受一个类型为B*的参数,并且在调用该函数指针时应该提供一个指向B的指针。

要正确使用该扩展,您应该执行以下操作:

typedef void (*my_function)(B*);
auto some_func = (my_function)(b.*h);
some_func(&b);

这是一个非标准的GNU扩展,不受Clang支持。据我所知,没有办法让Clang接受这段代码。

我报告了一个 GCC 错误,说你滥用扩展应该是一个错误。

显然在 g++ 中,它允许您将指向成员函数的指针转换为函数指针,并且调用它将导致使用损坏/丢失的this指针调用该成员函数。

此外,表达式b.*m,其中m是成员函数,b是一个类,将评估虚拟调度,然后可以将生成的对象强制转换为已计算该虚拟调度的函数指针。 它仍将具有损坏的this值。

这两者都是标准的非法操作;程序要么定义不明确,要么使用它是不定义的行为。

在 g++ 中,this指针是垃圾这一事实是一个很好的理由,为什么这不是一个非常有用的策略。 下面是一个示例,它以B中的某种状态构建,并且不在 clang++ 上编译并在 g++ 上编译段错误。

成员函数和指向对象的指针是两件事。 将它们都存储在一个东西(函数指针)中是行不通的,无论你如何破解它。

有许多方法可以解决这个问题。 最简单的是使用std::function,但禁止您使用标准库。

下一个最简单的方法是编写自己的std::function变体。 我已经做到了 - 一个"委托",它支持存储指向lambda的指针,指向函数的指针(指向对象的指针+指向成员函数的指针),这些指针与给定签名兼容。

所有这些都是微不足道的"普通旧数据",因此您可以创建一个简单的缓冲区,将它们的值复制到缓冲区中,并保存一个知道如何获取缓冲区指针并调用它的函数。

template<class Sig, unsigned Sz=3*sizeof(void*)>
struct fun;
template<class R, class...Args, unsigned Sz>
struct fun<R(Args...), Sz> {
using invoker_t = R(*)(void*, Args&&...);
invoker_t invoker;
unsigned char buffer[Sz];
template<class D>
struct invoker_base_t {
using invoker_sig = R(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->R {
return (*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class T, class M>
struct mem_fun_invoker_t:invoker_base_t<mem_fun_invoker_t<T,M>> {
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
R operator()(Args&&...args) {
return (t->*m)(static_cast<Args&&>(args)...);
}
};
template<class T, class M>
static mem_fun_invoker_t<T, M> mem_fun_invoker(T* t, M m){ return {t, m}; }
template<class T>
struct ptr_invoker_t:invoker_base_t<ptr_invoker_t<T>> {
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
R operator()(Args&&...args) {
return (*t)(static_cast<Args&&>(args)...);
}
};
template<class T>
static ptr_invoker_t<T> ptr_invoker(T* t){ return {t}; }
template<class T, class U, class Ret, class...Ts>
fun(T* t, Ret(U::*mem)(Ts...)) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class T, class U, class Ret, class...Ts>
fun(T const* t, Ret(U::*mem)(Ts...)const) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class Ret, class...Ts>
fun(Ret(*f)(Ts...)) {
auto invoke = ptr_invoker( f );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
// TODO: SFINAE
template<class Lambda>
fun(Lambda&& lambda) {
auto invoke = ptr_invoker( &lambda );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
R operator()(Args...args) {
return invoker(&buffer, static_cast<Args&&>(args)...);
}
explicit operator bool()const {return invoker;}
};

以上需要质量扫描和更多的断言和一些SFINAE。

但它有效。

fun<Sig>是几乎没有状态的std::function的裸骨变体。 缺少的 SFINAE 需要测试Lambda是否与签名兼容并且不等于fun类型本身。

这些对象的大小大约为 4 个指针;您可以使用Sz参数配置大小。 2 可能足以适合成员函数和this指针。 我四舍五入到 3。

这些东西还可以存储指向函数的指针。

它们可以存储指向 lambda 的指针,或fun的变体,但不会延长所述 lambda 的生命周期。 这很危险,但通常很有用。

std::function一样,它们不需要签名来完全匹配。 与C++14的std::function不同,如果Rvoid,存储的对象也必须返回void。 解决此问题需要做更多的工作:

namespace details {
template<class Sig, class D>
struct invoker_base_t;
template<class R, class...Args, class D>
struct invoker_base_t<R(Args...), D> {
using invoker_sig = R(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->R {
return (*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class...Args, class D>
struct invoker_base_t<void(Args...), D> {
using invoker_sig = void(*)(void* self, Args&&...args);
static invoker_sig invoker() {
return [](void* self, Args&&...args)->void {
(*static_cast<D*>(self))(static_cast<Args&&>(args)...);
};
}
};
template<class Sig, class T, class M>
struct mem_fun_invoker_t;
template<class R, class...Args, class T, class M>
struct mem_fun_invoker_t<R(Args...), T, M>:
invoker_base_t<R(Args...), mem_fun_invoker_t<R(Args...),T,M> >
{
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
R operator()(Args&&...args) {
return (t->*m)(static_cast<Args&&>(args)...);
}
};
template<class...Args, class T, class M>
struct mem_fun_invoker_t<void(Args...), T, M>:
invoker_base_t<void(Args...), mem_fun_invoker_t<void(Args...),T,M> >
{
T* t;
M m;
mem_fun_invoker_t(T* tin, M min):t(tin), m(min) {}
mem_fun_invoker_t(mem_fun_invoker_t const&)=default;
void operator()(Args&&...args) {
(t->*m)(static_cast<Args&&>(args)...);
}
};
template<class Sig, class T>
struct ptr_invoker_t;
template<class R, class...Args, class T>
struct ptr_invoker_t<R(Args...), T>:
invoker_base_t<R(Args...), ptr_invoker_t<R(Args...),T> >
{
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
R operator()(Args&&...args) {
return (*t)(static_cast<Args&&>(args)...);
}
};
template<class...Args, class T>
struct ptr_invoker_t<void(Args...), T>:
invoker_base_t<void(Args...), ptr_invoker_t<void(Args...),T> >
{
T* t;
ptr_invoker_t(T* tin):t(tin) {}
ptr_invoker_t(ptr_invoker_t const&)=default;
void operator()(Args&&...args) {
(*t)(static_cast<Args&&>(args)...);
}
};
}
template<class Sig, unsigned Sz=3*sizeof(void*)>
struct fun;
template<class R, class...Args, unsigned Sz>
struct fun<R(Args...), Sz> {
using invoker_t = R(*)(void*, Args&&...);
invoker_t invoker;
unsigned char buffer[Sz];
template<class T, class M>
static details::mem_fun_invoker_t<R(Args...), T, M> mem_fun_invoker(T* t, M m){ return {t, m}; }
template<class T>
static details::ptr_invoker_t<R(Args...), T> ptr_invoker(T* t){ return {t}; }
template<class T, class U, class Ret, class...Ts>
fun(T* t, Ret(U::*mem)(Ts...)) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class T, class U, class Ret, class...Ts>
fun(T const* t, Ret(U::*mem)(Ts...)const) {
auto invoke = mem_fun_invoker( t, mem );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
template<class Ret, class...Ts>
fun(Ret(*f)(Ts...)) {
auto invoke = ptr_invoker( f );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
// TODO: SFINAE
template<class Lambda>
fun(Lambda&& lambda) {
auto invoke = ptr_invoker( &lambda );
static_assert(sizeof(invoke) <= Sz);
::new( (void*)buffer ) decltype(invoke)(invoke);
invoker = invoke.invoker();
}
R operator()(Args...args) {
return invoker(&buffer, static_cast<Args&&>(args)...);
}
explicit operator bool()const {return invoker;}
};

活生生的例子。

以上符合标准,不依赖于std。 编写 SFINAE 以确保在不良情况下不会调用Lambda&&过载可能需要重写一些std,例如std::is_sameenable_if