0

I want the Parent class to pass a pointer to a callback function to the child class when the child class is created. I got this working when the parent was the sketch thanks to this post:
In this example, the parent was the .ino file, i.e. not a class but a collection of functions. This code worked fine. However, I need to be able to make the call to the child constructor from a within a class as shown in the code below. I get I get an "invalid use of non-static member function on the line child = new Child ( handlerDispatcher );

Here is the complete code:

class Child {
public:
    typedef void ( *callback_t ) ( uint8_t, uint8_t );

    callback_t callback;

    //  Constructor
    Child ( callback_t _callback ): callback ( _callback ) {}

    void doCallback ( uint8_t id, uint8_t index ) {
        callback ( id, index );
    }

    void makeCallbacks () {
        doCallback ( 1, 0 );
        doCallback ( 2, 1 );
        doCallback ( 3, 2 );
    }
};

class Parent {
public:
    Child *child;

    Parent () {}

    void handlerDispatcher ( uint8_t id, uint8_t index ) {
        ( this->*callbackHandlers [ index ] ) ( id );
    }

    typedef void ( Parent:: *handlerPointer ) ( uint8_t );
    handlerPointer callbackHandlers [ 3 ] = {
        &Parent::handler1,
        &Parent::handler2,
        &Parent::handler3,
    };

    void handler1 ( uint8_t index ) { echo ( index ); }
    void handler2 ( uint8_t index ) { echo ( index ); }
    void handler3 ( uint8_t index ) { echo ( index ); }
    void echo ( uint8_t value ) {
        Serial.print ( "Got: " );
        Serial.println ( value );
    }

    void configure () {
        child = new Child ( handlerDispatcher );

    }
    void test () {
        child->makeCallbacks ();
    }
};

Parent parent;
void setup () {
    Serial.begin ( 115200 );
    parent.configure ();
    parent.test ();
}

void loop() {}

2 Answers 2

1

Use virtual functions. On object of type Child, fnc2 defined in Parent will call fnc1 of class Child:

class Parent {
public:

  virtual void fnc1() {
  }

  void fnc2 {
    ...
    fnc1();
    ...
  } 
}

class Child : public Parent {
public:
  virtual void fnc1() {
    ...
  }
}
3
  • For the child drop the virtual and append a final - unless you want to have grandchildren. Also you could make the parent's fnc1() pure virtual.
    – Majenko
    Commented Aug 31, 2019 at 9:30
  • @Majenko, and then I would have to explain it :-)
    – Juraj
    Commented Aug 31, 2019 at 10:58
  • I have expanded my answer to do just that.
    – Majenko
    Commented Aug 31, 2019 at 10:59
0

It is because

void Parent::handlerDispatcher ( uint8_t id, uint8_t index )

is not

void ( *callback_t ) ( uint8_t, uint8_t )

The closest equivalent would be:

void ( *callback_t ) (Parent *, uint8_t, uint8_t )

Though I am not sure off hand if that is even possible.

But it seems you are trying to do something insanely convoluted and complex which would be made far simpler if you just used object inheritance and forget about callbacks.

With object inheritance a child object can implement the functions that a parent object defines. That way you end up with a single object instance which is a combination of the parent functions and the child functions, one overlaid on the other.

To build on Juraj's excellent answer and add a bit of explanation, if you take the following parent object:

class Parent {
public:

  virtual void fnc1() = 0;

  void fnc2 {
    fnc1();
  } 
};

What we have here are two functions in the parent. fnc2() simply calls fnc1() as an example. However fnc1() isn't a normal function - it's a pure virtual function. That is akin to an interface in Java. It's a placeholder in the parent that says "This function will exist, but the child will implement it, not me". If you try and instantiate a Parent object the compiler will complain that certain functions are pure virtual and the object can't be created.

So then you create a child class which takes the parent class and extends it with the body of any pure virtual functions that are needed, and can also add more. For example:

class Child : public Parent {
public:
  void fnc1() final {
    ...
  }
};

Here we only have one function - fnc1(), which is marked as final. The final keyword is kind of the opposite of the virtual keyword. virtual means "This function will be overridden by the child". final means "I have overridden this, and nothing else can now override it". It's not essential, but it does help with the compiler's optimiser somewhat, since it no longer needs to worry about further inheritance of that function.

So now you can create a Child instance, and you will have access to all the parent functions and data, as well as all the child functions and data. Simply calling fnc2() on this child instance will call that function on the parent, which then itself calls fnc1() which is implemented by the child.

And thanks to something called polymorphism, the child can be seen as a parent for all intents and purposes (though if you look at it as a parent you won't see any of the child's own functions).

To wrap it all up, here's a full example that uses two different child classes and stores pointers to them in an array of parents (so the children are seen as if they are objects of class Parent), iterates through them, and calls the parent's fnc2() function:

class Parent {
    public:
        virtual void fnc1() = 0;
        void fnc2() {
            fnc1();
        }
};

class Child1 : public Parent {
    public:
        void fnc1() final {
            Serial.println("I am child 1");
        }
};

class Child2 : public Parent {
    public:
        void fnc1() final {
            Serial.println("I am child 2");
        }
};

Child1 c1;
Child2 c2;

Parent *objects[2] = {
    &c1, &c2
};

void setup() {
    Serial.begin(115200);
    for (int i = 0; i < 2; i++) {
        objects[i]->fnc2();
    }
}

void loop() { }

The serial output from that program is:

I am child 1
I am child 2

Which I think is the end effect that you are looking for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.