mardi 26 novembre 2019

pointer-to-method callbacks in C++11/14/17?

(A previous question of mine has similar wording and examples, but is asking something quite different. Before I was asking for ideas for approaches. Now I'm asking how to get a specific approach to work.)

I have some subscription function that will call my callback when something happens. (Let's say it's a timer, and will pass me an object when a certain number of milliseconds elapses.) After looking at lambdas, std:function and std:bind I think the solution of pointers-to-methods is higher performance and simpler to write (especially for the subscriber) but I can't quite figure out the last bit.

This example mirrors my project a bit: we have a framework, represented by Foo, which is written once, and we'll have many subclasses represented here by Bar that will be written by people with more domain knowledge but less C++ knowledge. So, we want that call to SubscribeTimer() to be as simple as possible. Finally the application is high performance and we'd like to eliminate heap usage, including creating implicit std::bind objects and so on.

#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();
}

As expected (if you overlook the requirement to write Foo::, not Bar:: in Init()'s call to SubscribeTimer()) the program outputs:

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

So this example works perfectly and does what I need... except that the subclass can only register handlers for method names the superclass has thought to define, whether or not they are defined or not. In reality, though, the subclass may wish to register many handlers for different events, with names tha superclass wouldn't know.

So in a sentence: how can I pass OnTimerB() into the method-pointer version of SubscribeTimer()? I'm happy to change Timer_T definition, SubscribeTimer() or whatever. As a floor though, there is no point in a member-pointer solution more complicated for the subclass than the std::function implementation, and no point in a solution slower than thestd::function implementation either. On the other hand, added complexity in the superclass isn't a problem, as it's write-once code.

Aucun commentaire:

Enregistrer un commentaire