【问题标题】:C++ Function pointer arguments and classes inheritance automatic castC++ 函数指针参数和类继承自动转换
【发布时间】:2019-12-13 10:32:51
【问题描述】:

首先抱歉,如果问题的名称不够清楚。我真的不知道如何称呼这个问题。

所以我在一个类中有一个函数指针,它像一个 java 回调一样工作,我使用一些参数调用它,比如它本身是从父类派生的,就像在这个例子中一样:

class Parent;

using f_Call = void(*)(Parent*);

class Parent
{
public:
    void setCallback(f_Call call)
    {
        mOnCall = call;
    }

protected:
    f_Call mOnCall = nullptr;
};

class Child1 : Parent
{
public:
    void doSomething()
    {
        // some work..
        if (mOnCall)
            mOnCall(this);
    }
};

void onCallExe(Parent* p)
{
    Child1* child = (Child1*)p;
    // do some more work...
}

int main()
{
    Child1 child;
    child.setCallback(onCallExe);
    child.doSomething();
}

我的问题是,c++ 是否有办法在 onCallExe 中自动进行从父级到子级的强制转换,所以我不必为我调用的每个函数都这样做。

谢谢!

【问题讨论】:

  • 如果你总是想在回调中使用Child*,为什么不相应地更改签名呢?
  • 嗨@dave,我的项目中有 16-18 个子类,它们最终会执行该函数。
  • 在这种情况下,我认为没有自动的方法可以做到这一点。 (编译器如何知道要为任何给定的回调函数转换到哪个子类?)在我看来,您可能希望在 Parent 类中添加更多虚函数,因此您根本不需要转换。
  • 听到这个消息很难过。无论如何,感谢您的帮助!
  • 您不想使用函数指针。你想要std::function<void()>(是的,没有参数)并且你想要传递一个带有捕获的对象的lambda。

标签: c++ class inheritance callback


【解决方案1】:

不要使用函数指针。相反,您想要std::function<void()>(是的,没有参数)并传递一个带有捕获对象的lambda。

using f_Call = std::function<void()>;

class Parent { 
public:
      void setCallback(f_Call call) {
           mOnCall = call;
      }
      protected:
          f_Call mOnCall;
};

class Child1 : public Parent {
public:
      void doSomething() {
           // some work..
           if (mOnCall)
               mOnCall(); // no argument!
       }
};

int main()  {
    Child1 child;
    child.setCallback([&child](){ /* do whatever with the child */ });
    child.doSomething();
}

如果需要,您可以在函数模板中隐藏 lambda 的创建。

template <class Obj, class CB>
void setCallback (Obj& obj, CB cb) {
     obj.setCallback([&obj](){cb(obj);});
}

然后将任何带有 Child 参数的旧函数传递给全局 setCallback 模板。

void onCallExe(Child1& child) { 
    // do some more work...
}

Child1 child;
setCallback(child, onCallExe);

【讨论】:

    【解决方案2】:

    Curiously recurring template pattern 可能是一个选项。但是,您将为每个派生类创建单独的基类,因此您不能多态地使用它们——除非您提供一个单独的公共基类。如果循环模板函数覆盖了基础中的一个虚拟函数,您可能最终会到达您想要获取的位置:

    struct Base
    {
        virtual ~Base() { }
        virtual void f() = 0;
    };
    
    template <typename T>
    struct Intermediate : Base
    {
        void f() override
        {
            if(callback)
                callback(static_cast<T*>(this));
        }
        void setCallback(void(*c)(T*))
        {
            callback = c;
        }
    
    private:
        void(*callback)(T*) = nullptr;
    };
    
    struct Derived : Intermediate<Derived>
    {
    };
    
    void g(Derived*) { }
    
    void demo()
    {
        Derived d;
        d.setCallback(g);
        d.f();
    }
    

    (如果你不需要多态,你可以跳过Base 类——那么f 也不必是虚拟的。)

    如果你想通过指针或引用 Base 设置回调,你会有点麻烦,因为你不能有虚拟模板成员函数。您可以提供一个独立的辅助函数:

    template <typename T>
    void setCallback(Base& b, void(*callback)(T*))
    {
        dynamic_cast<Intermediate<T>&>(b).setCallback(callback);
    }
    

    如果b 的类型不合适,动态转换将抛出std::bad_cast - 不幸的是,这是一个相当昂贵的运行时事情,是让编译器确定指向/引用对象是否是正确类型的安全方法(通常)不可能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2012-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多