【问题标题】:Is it safe to upcast a function pointer?向上转换函数指针是否安全?
【发布时间】:2013-02-19 15:38:16
【问题描述】:

我有基类ObjectEvent

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 中肯定可以正常工作。

【问题讨论】:

    标签: c++ member-function-pointers upcasting


    【解决方案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,你会发现它并没有像你希望的那样工作。


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

    理想情况下,您只需修复签名以同时获取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 兼容。

    【讨论】:

    • 或者您可以将Dispatcher 设为模板,将Event 的具体子类作为参数,以在编译时强制执行成员仅与兼容类型的事件对象一起使用的承诺。
    • @aschepler:实际上,Dispatcher 目前对我来说毫无用处,所以我宁愿不谈论它 :)
    猜你喜欢
    • 2012-01-27
    • 2013-03-17
    • 1970-01-01
    • 2014-09-22
    • 1970-01-01
    • 1970-01-01
    • 2011-05-15
    • 2011-04-03
    • 1970-01-01
    相关资源
    最近更新 更多