0

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.) The thing I want to be called is a virtual method. I feel std::function and std::bind or lambdas are part of the solution.

The C++99 approach I've used until now involves one-line C functions that know how to call a virtual method. The subscription function takes the C function and a void* user data as arguments. For example:

class Foo {
  virtual void OnTimerA( Data* pd );
};

void OnTimerACB( Data* pd, void* pvUserData ) {
  ( (Foo*) pvUserData )->OnTimerA( pd );
}

/* Inside some method of Foo; 1000 is a number of milliseconds to call me back in;
   second arg is a function pointer; third is a void* user data that is passed back
   to the C callback. */
SubscribeToTimerOld( 1000, OnTimerACB, this );

What I'm hoping for is a way to write:

SubscribeToTimerNew( 1000, OnTimerA );

or something similar, at least that disposes of the need to write that one-line C binding callback.

I have a feeling that SubscribeToTimerNew()'s argument is probably a std:function of some sort and instead of merely writing OnTimerA I'd have to write something with std::bind to get the this pointer in there.

Alternatively to bind, perhaps a lambda is the way to do it? This compiles, though I dont see how to extend it to let the event handler pass an argument to OnTimerA(). (My linker isn't currently working so don't know if it links or runs as desired.)

SubscribeTimer(  1000, [this](){this->OnTimerA();} );

To mention one alternative I've discarded: give Foo a superclass with a method called OnTimer() that will be called when the timer goes off. Now SubscribeTimer() only need take an elapsed time. I don't like this as it doesn't cleanly allow for multiple timers to be registered. If it did you could give them (say) integer timer ID's and implement OnTimer() as a switch but this seems to be a lot more complicated than the C++99 solution.

Ultimately of the (I assume) several approaches, are there any trade-offs (e.g., heap use) in addition the most obvious question of how much typing is involved? (This is a high-performance application and I'd prefer to minimize or eliminate heap usage.)

Swiss Frank
  • 1,985
  • 15
  • 33
  • Have a read about performance comparison: https://stackoverflow.com/questions/14306497/performance-of-stdfunction-compared-to-raw-function-pointer-and-void-this tldr quote from accepted answer: "a C++11's std::function call just adds 2 more CPU instructions, thus 5 in our example. As a conclusion: it absolutely doesn't matter what way of function pointer technique you use, the overhead differences are in any case very small." – R2RT Nov 21 '19 at 10:30

1 Answers1

0

C++11, C++14 and C++17 are quite different, especially when it comes to lambdas. And lambdas are a great way to create callbacks. For instance, see Why use std::bind over lambdas in C++14?

Using modern C++, you can use std::function as your callback type and then you can use any callable stuff as an actual callback. Quote from https://en.cppreference.com/w/cpp/utility/functional/function:

Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.

Example:

#include <functional>
#include <iostream>

using Callback = std::function<void(int)>;

void subscribe(Callback callback, int duration) {
    callback(duration);
}

struct Foo {
    void operator()(int duration) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << duration << '\n';
    }
};

struct Bar {
    virtual void myFunction(int duration) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << duration << '\n';
    }
};

void freeFunction(int duration) {
    std::cout << __PRETTY_FUNCTION__ << ' ' << duration << '\n';
}


struct Zorg {
    static void staticFunction(int duration) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << duration << '\n';
    }
};

int main() {
    Foo foo;
    subscribe(foo, 128);

    Bar bar;
    auto lambda = [&bar](int duration) {
        bar.myFunction(duration);
    };
    subscribe(lambda, 256);

    subscribe(freeFunction, 512);

    subscribe(Zorg::staticFunction, 1024);
}

Output:

void Foo::operator()(int) 128

virtual void Bar::myFunction(int) 256

void freeFunction(int) 512

static void Zorg::staticFunction(int) 1024

Bktero
  • 722
  • 5
  • 15
  • You could improve your answer by implementing actual subscription, not just invoking passed `std::function` in `subscribe`. It would be show case that `std::function` can be stored for later use, what is the purpose of callbacks. – R2RT Nov 21 '19 at 10:23