markzzz
markzzz

Reputation: 47995

What's wrong with this Callback?

This is my code:

#include <iostream>
#include <stdio.h>

using namespace std;

typedef void (*CallbackType)(double); 

class LFO
{
private:

public:
    CallbackType target;

    inline void Process() {
        double value = 1.3;
        target(value);
    }
};

class ExternalObject
{
private:

public:
    double mTest = 1.0;

    ExternalObject() {

    }
    ~ExternalObject() {

    }

    inline void PrintValue(double value) {
        cout << "passed: " << value << endl;
    }
};

int main ()
{
    ExternalObject eo;
    LFO lfo;

    lfo.target = eo.PrintValue;
    lfo.Process();
}

I want to execute within LFO's Process the callback PrintValue of ExternalObject. But it seems I cannot pass lfo.target in this way? Where am I wrong? (sorry, I'm new to C++; in JavaScript this is the way I'll do it).

Upvotes: 1

Views: 200

Answers (1)

Mr.C64
Mr.C64

Reputation: 42994

The CallbackType is defined like this:

typedef void (*CallbackType)(double); 

This basically is a function taking a double and returning nothing (void).

When you assign eo.PrintValue like this:

lfo.target = eo.PrintValue;

you have a type mismatch: That's because PrintValue is a non-static member function of the ExternalObject class. Non-static member functions have a "hidden" additional parameter, which is the this pointer to the class instance.

It's kind of like the actual declaration of PrintValue would be:

void ExternalObject::PrintValue(ExternalOject* this, double value) 

You can fix that making PrintValue a static method of ExternalObject, as static methods don't have the additional this pointer parameter. E.g.:

class ExternalObject {
 public:
  // Note: you don't need "inline" here
  static void PrintValue(double value) {
     ...
  }

P.S. Usually when you have callbacks, a common pattern in C++ is to provide an additional void* parameter in the callback. This can be used to give some context to the callback function, including passing the aforementioned this pointer (so a static member-function callback can use the this pointer to call a non-static method).

P.P.S. That callback style is usually found in C-interface APIs. In C++ you may want to consider std::function (e.g. std::function<void(double)>) instead.


You may also consider a design with a class with overloaded operator() as callback (so called "functor", or "function object"), e.g.:

#include <functional>  // for std::function
#include <iostream>

class LFO {
public:
    std::function<void(double)> Target;

    void Process() {
        double value = 1.3;
        Target(value);
    }
};

class ValuePrinter {
public:
    void operator()(double value) const {
        std::cout << "passed: " << value << '\n';
    }
};

int main() {
    LFO lfo;
    lfo.Target = ValuePrinter();
    lfo.Process();
}

As a different alternative, you can use std::bind and a placeholder for the double parameter, e.g. inside main:

ExternalObject eo;
LFO lfo;

using namespace std::placeholders;
lfo.Target = std::bind(&ExternalObject::PrintValue, &eo, _1);
lfo.Process();

Compilable code:

#include <functional>
#include <iostream>

class LFO {
public:
    std::function<void(double)> Target;

    void Process() {
        double value = 1.3;
        Target(value);
    }
};

class ExternalObject {
public:
    double mTest = 1.0;

    ExternalObject() = default;

    void PrintValue(double value) {
        std::cout << "passed: " << value << '\n';
    }
};

int main() {
    ExternalObject eo;
    LFO lfo;

    using namespace std::placeholders;
    lfo.Target = std::bind(&ExternalObject::PrintValue, &eo, _1);
    lfo.Process();
}

Upvotes: 1

Related Questions