向上转换函数指针是不是安全?
Posted
技术标签:
【中文标题】向上转换函数指针是不是安全?【英文标题】:Is it safe to upcast a function pointer?向上转换函数指针是否安全? 【发布时间】:2013-02-19 15:38:16 【问题描述】:我有基类Object
和Event
class Object
//...
;
class Event
;
还有一个函数指针的类型定义
typedef void (Object::*PF) (Event*);
还有一个存储两个指针的无关类
class Dispatcher
public:
Dispatcher (Object* obj, PF pf) : _obj (obj), _pf (pf)
void call (Event* event)
_obj->*pf (event);
Object* _obj;
PF _pf;
;
然后我有一个具体的对象和一个具体的事件
class ConcreteEvent : public Event
;
class ConcreteObject : public Object
public:
void handle (ConcreteEvent* event)
// Do something specific for ConcreteEvent
;
然后这样称呼它
ConcreteObject* obj = new ConcreteObject();
Dispatcher dispatcher (obj, static_cast<PF>(&ConcreteObject::handle));
ConcreteEvent* event = new ConcreteEvent ();
dispatcher.call (event);
我保证调度器总是会被正确的事件调用,即当它封装的函数指针实际上采用SomeOtherConcreteEvent
时,我不会调用调度器并传递它ConcreteEvent
问题是:这能保证有效吗?在 linux 和 mingw 上的 gcc 4.7 中肯定可以正常工作。
【问题讨论】:
【参考方案1】:来自 C++11 标准,第 4.11.2 节:
“指向类型为 cv T 的 B 的成员的指针”类型的纯右值,其中 B 是类类型,可以转换为 类型“指向 cv T 的 D 成员的指针”类型的纯右值,其中 D 是 B 的派生类(第 10 条)。如果 B 是 D 的不可访问(第 11 条)、模棱两可(10.2)或虚拟(10.1)基类,或虚拟的基类 D 的基类,需要这种转换的程序格式错误。转换的结果是指 在转换发生之前指向与指向成员的指针相同的成员,但它指的是基 类成员,就好像它是派生类的成员一样。
所以是的,这应该是安全的。
编辑:因此,如果您实际上是指 向下转换:,根据 C++11 5.2.9.12,这也是合法的:
类型为“指向 cv1 T 的成员的指针”类型的纯右值可以转换为类型为“指向的指针”的纯右值 类型 cv2 T 的 B”的成员,其中 B 是 D 的基类(第 10 条),如果从 “指向T类型B成员的指针”到“指向T类型D成员的指针”存在(4.11),与cv2相同 cv 限定为或大于 cv1。 69
【讨论】:
代码使用了这种转换的逆向,static_cast
明确允许这种转换。结果只能用于成员的实际类类型的对象,但 OP 已经说过他对此很小心。
@aschepler 标准中还有另一节。然而,这就是所谓的“向上转型”。逆向是“向下转换”(从派生类的成员转换为基类的成员时)。
用指向成员的指针调用什么可能会让人感到困惑,但第 4 节描述了可以隐式使用的标准转换,而这段代码需要相反的,不能隐式使用。
@aschepler 但是这里没有人谈论隐式类型转换。 OP 很好奇 cast 是否可行。
我只是指出,OP 中的示例代码包含一个指针到成员的转换,你称之为“向下转换”,而没有指针到成员的转换,你称之为“向上转换”。
【参考方案2】:
我认为它是不安全的,有两个原因。
首先是指向成员函数的指针只能安全地向下传播(因为Derived
类必然继承了基类的函数,而反之则不然)。
class Base
public:
void foo();
; // class Base
class Derived: public Base
public:
void bar();
;
using PBase = void (Base::*)();
using PDerived = void (Derived::*)();
int main()
PBase pb = &Base::foo;
PDerived pd = &Derived::bar;
pb = pd; // error: cannot convert 'PDerived aka void (Derived::*)()'
// to 'PBase aka void (Base::*)()' in assignment
pd = pb;
(如here所见)
第二个是你不能像那样改变参数的类型。为了说明这个问题,使用ConcreteObject: public virtual Object
,你会发现它并没有像你希望的那样工作。
现在,这并不意味着你想做的事情是不可能的,只是它需要多一点。
理想情况下,您只需修复签名以同时获取Object
和Event
,而不是使用成员函数,然后在必要时让它处理手动转换:
using Function = std::function<void(Object*,Event*)>;
void handle(Object* o, Event* e)
ConcreteObject* co = dynamic_cast<ConcreteObject*>(o);
ConcreteEvent* ce = dynamic_cast<ConcreteEvent*>(e);
if (co and ce) co->handle(ce);
或者任何你觉得舒服的施法/检查。
注意:使用 std::function
是为了与 lambdas/functors 兼容。
【讨论】:
或者您可以将Dispatcher
设为模板,将Event
的具体子类作为参数,以在编译时强制执行成员仅与兼容类型的事件对象一起使用的承诺。
@aschepler:实际上,Dispatcher
目前对我来说毫无用处,所以我宁愿不谈论它 :)以上是关于向上转换函数指针是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章