存储指向成员函数的指针不适用于 clang++
Posted
技术标签:
【中文标题】存储指向成员函数的指针不适用于 clang++【英文标题】:storing pointer to member function does not work for clang++ 【发布时间】:2017-03-29 18:03:20 【问题描述】:当我使用 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 函数,因为代码在裸机上运行
【问题讨论】:
然后你向 C 标签发送了垃圾邮件 - 为什么? 您将b.*h
转换为my_function
类型,您希望这会做什么?请注意,上面的代码显然不能在 g++ 中编译和运行,您至少缺少;
。 minimal reproducible example 请。
您不能将非静态成员方法指针强制转换为函数指针。成员方法指针需要一个实例 (this
),函数指针不指向该实例。指针some_func
必须以某种方式捆绑b
。
请注意(如果您更正return
处的错误)gcc 会生成有关您的演员阵容的警告my_function
。 Visual Studio 2015 也拒绝代码并出现类似错误。
但是没有调用任何在 g++ 中编译但不是 clang++ 的成员变量,这是 g++ 错误吗?看看这个gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html
【参考方案1】:
您在 g++ 中滥用了 Bound member functions 扩展名。您正在转换为错误类型的函数指针,它具有未定义的行为:
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 bug 说您滥用扩展程序应该是一个错误。
【讨论】:
【参考方案2】:显然,在 g++ 中,它允许您将一个指向具有 0 个参数的成员函数的指针转换为函数指针,并且调用它会导致调用带有损坏/丢失的 this
指针的该成员函数。
此外,表达式b.*m
,其中m
是一个成员函数,b
是一个类将评估虚拟调度,然后可以将生成的对象强制转换为已评估该虚拟调度的函数指针.它仍然会有一个损坏的this
值。
这两种都是标准下的非法操作;程序要么定义不正确,要么使用它是未定义的行为。
在 g++ 中,this
指针是垃圾这一事实是这不是一个非常有用的策略的一个很好的理由。 Here 是一个在 B
中以某种状态构建的示例,它不能在 clang++ 上编译并且在 g++ 上出现段错误。
成员函数和指向对象的指针是两件事。将它们存储在一个东西(函数指针)中是行不通的,无论你如何破解它。
有很多方法可以解决这个问题。最简单的就是使用std::function
,但是你被禁止使用标准库。
下一个最简单的方法是编写自己的std::function
变体。我已经做到了——一个支持存储指向 lambdas 的指针、指向函数的指针(指向对象的指针 + 指向成员函数的指针)的“委托”,这些指针与给定的签名兼容。
所有这些都是微不足道的“普通旧数据”,因此您可以创建一个简单的缓冲区,将它们的值复制到缓冲区中,并保存一个知道如何获取指向缓冲区的指针并调用它的函数。
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。
But it works.
fun<Sig>
是std::function
的基本变体,几乎没有状态。缺少的 SFINAE 需要测试 Lambda
是否与签名兼容并且不等于 fun
类型本身。
这些对象的大小大约为 4 个指针;您可以使用 Sz
参数配置多大。 2 可能足以容纳成员函数和 this
指针。我四舍五入到 3。
这些东西也可以存储指向函数的指针。
它们可以存储指向 lambda 的指针或fun
的变体,但不会延长所述 lambda 的生命周期。这很危险,但通常很有用。
与std::function
一样,它们不需要签名完全匹配。与 C++14 的 std::function
不同,如果 R
是 void
,则存储的对象也必须返回 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;
;
live example.
以上内容符合标准,不依赖于std
。编写 SFINAE 以确保在糟糕的情况下不会调用 Lambda&&
重载可能需要重写一些 std
,例如 std::is_same
和 enable_if
。
【讨论】:
以上是关于存储指向成员函数的指针不适用于 clang++的主要内容,如果未能解决你的问题,请参考以下文章