【问题标题】:pointer-to-method callbacks in C++11/14/17?C++ 11/14/17 中的指向方法回调?
【发布时间】:2019-11-26 08:11:20
【问题描述】:

(我之前的一个问题有类似的措辞和例子,但问的是完全不同的东西。 在我寻求方法的想法之前。现在我在问如何获得特定的工作方法。)

我有一些订阅函数,当有事情发生时会调用我的回调。 (假设它是一个计时器,当经过一定的毫秒数时会传递给我一个对象。)查看 lambdas,std:functionstd:bind 我认为指针到方法的解决方案性能更高,更简单写(特别是对于订阅者),但我不太清楚最后一点。

这个例子有点像我的项目:我们有一个框架,由 Foo 表示,它只编写一次,我们将有许多由 Bar 表示的子类,这些子类将由具有更多领域知识但较少 C++ 知识的人编写.因此,我们希望对SubscribeTimer() 的调用尽可能简单。最后,应用程序是高性能的,我们希望消除堆使用,包括创建隐式 std::bind 对象等。

#include <iostream>
#include <functional>
using namespace std;

class Data { int i; };

class Foo {
public:
    typedef void (Foo::*Timer_T)( Data* pd );
    virtual void SubscribeTimer( int iMilliseconds, Timer_T pmethod );
    virtual void SubscribeTimer( int iMilliseconds, std::function<void(Data*)> pfn ); // undesired

    virtual void OnTimerA( Data* pd ) { cout << "Foo::OnTimerA called" << endl; };
};

void Foo::SubscribeTimer( int iMilliseconds, Timer_T pmethod ) {
    Data d;
    (this->*pmethod)( &d );
}

void Foo::SubscribeTimer( int iMilliseconds, std::function<void(Data*)> pfn ) { // undesired
    Data d;
    pfn( &d );
}

class Bar: public Foo {
    public:
    void Init();
    virtual void OnTimerA( Data* pd ) { cout << "Bar::OnTimerA called" << endl; };
    virtual void OnTimerB( Data* pd ) { cout << "Bar::OnTimerB called" << endl; };
};

void Bar::Init() {
    // Works like I want it to: easy to subscribe, and high performance.
    SubscribeTimer( 1000, &Foo::OnTimerA );
    // What I'd like to do, but doesn't work.
    //SubscribeTimer( 1000, &Bar::OnTimerB );
    // Does exactly what I want except more complicated to write and I believe slower to run.
    SubscribeTimer( 1000, std::bind( &Bar::OnTimerB, this, std::placeholders::_1 ) );
}


int main( int nArg, const char* apszArg[] ) {
    Bar bar;
    bar.Init();
}

正如预期的那样(如果您忽略了在Init()SubscribeTimer() 的调用中写入Foo:: 而不是Bar:: 的要求)程序输出:

Bar::OnTimerA called  (from the version of SubscribeTimer() I like)
Bar::OnTimerB called  (from the version of SubscribeTimer() I think is too verbose/slow)

所以这个例子完美地工作并且满足我的需要...... 除了子类只能为超类认为定义的方法名称注册处理程序,无论它们是否被定义。但实际上,子类可能希望为不同的事件注册许多处理程序,其名称是超类不知道的。

所以一句话:如何将OnTimerB() 传递到SubscribeTimer() 的方法指针版本中?我很高兴更改 Timer_T 定义、SubscribeTimer() 或其他任何内容。尽管如此,对于子类来说,成员指针解决方案没有比std::function 实现更复杂的意义,而且比std::function 实现更慢的解决方案也没有意义。另一方面,超类中增加的复杂性不是问题,因为它是一次性代码。

【问题讨论】:

    标签: c++ c++11 c++14 c++17 stdbind


    【解决方案1】:

    要调用Bar::OnTimerB,您需要一个Bar 类型的指针。因此,您需要实现某种类型的擦除。

    class Foo {
    public:
      template <typename Derived>
      void SubscribeTimer(int iMilliseconds, void (Derived::*pmethod)(Data* pd)) {
        auto const self = static_cast<Derived*>(this);
        Data d;
        (self->*pmethod)(&d);
      }
    };
    

    显然,如果您要稍后调用回调,那么您需要以某种方式存储它。这就是std::function&lt;&gt; 为您抽象出来的内容。当然,您可以自己做,但std::function&lt;&gt; 还不错,尤其是在具有小功能优化的平台上。

    因此,您可以在内部执行 std::function 包装器:

      template <typename Derived>
      void SubscribeTimer(int iMilliseconds, void (Derived::*pmethod)(Data* pd)) {
        SubscribeTimer(iMilliseconds,
            [self = static_cast<Derived*>(this), pmethod](Data* pd) {
                (self->*pmethod)(pd);
            });
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-18
      • 1970-01-01
      • 2020-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多