向上投射函数指针安全吗

Is it safe to upcast a function pointer?

本文关键字:指针 安全 函数      更新时间:2023-10-16

我有基类Object和一个Event

class Object
{
//...
};
class Event
{
};

和一个函数指针的typedef

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上,Is在gcc 4.7中都可以正常工作。

来自C++11标准,第4.11.2节:

类型为"指向cv T类型的B的成员的指针"的prvalue,其中B是类类型,可以转换为类型为"指向cv T类型的D的成员的指针"的prvalue,其中D是B的派生类(第10条)。如果B是D的不可访问(第11条)、不明确(10.2)或虚拟(10.1)基类,或虚拟的基类D的基类,一个需要这种转换的程序是不正确的。转换的结果指指向与转换发生前指向成员的指针相同的成员,但它指的是基类成员,就好像它是派生类的成员一样。

所以是的,这应该是安全的。

编辑:因此,根据C++11 5.2.9.12:,如果您实际上是指向下广播:,那也是合法的

类型为"pointer to member of D of type cv1 T"的prvalue可以转换为类型为"pointer to类型为cv2T的"B的成员",其中B是D的基类(第10条),如果从存在"指向类型T的B的成员的指针"到"指向类型T的D的成员的指示器"(4.11),并且cv2是相同的cv资格等于或大于cv1。69

我认为不安全,原因有两个。

首先,指向成员函数的指针只能安全地向下传播(因为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;
}

(如图所示)

第二个问题是,不能像那样更改参数的类型。为了说明这个问题,使用ConcreteObject: public virtual Object,您会发现它并不像您希望的那样工作。


现在,这并不意味着你想做的事情是不可能的,只是它需要多一点

理想情况下,不使用成员函数,只需修复签名,使其同时接受ObjectEvent,然后在必要时让它处理手动强制转换:

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兼容