Kay Hennig
Kay Hennig

Reputation: 7

function pointer array with class functions of different classes

In SomeClass.h

class SomeClass{
    public:
        static std::vector<void (*)()> UpdateFuncs;
}

In OtherClass.h

class OtherClass{
    private:
        void Update();
    public:
        OtherClass();
}

In OtherClass.cpp

OtherClass::OtherClass(){
    Time::UpdateFuncs.push_back(&(this->Update));
}

On Build I get '&': illegal operation on bound member function expression and if I do:

.push_back(&Update);

Then I get "no instance of overloaded function

std::vector<_Ty, _Alloc>::push_back [with _Ty=void (*)(), _Alloc=std::allocator]" matches the argument list"

Thank in advance

Upvotes: 1

Views: 492

Answers (2)

Christophe
Christophe

Reputation: 73376

The problem

You try to assign to an ordinary function pointer of type void(*)() a member function pointer of type void(OtherClass::*)().

Unfortunately these two types are incompatible: an ordinary function can be called with only its parameters, and a member function pointer can only be called for a specific object.

First solution : member function pointer

You have to change the definition of the vector to make it use a member function pointer.

class OtherClass;  // forward deaclaration
class SomeClass {
public:
    static std::vector<void (OtherClass::*)()> UpdateFuncs;
};

Then you can pushback the function as expected:

OtherClass::OtherClass() {
    SomeClass::UpdateFuncs.push_back(&OtherClass::Update);
}

Online demo

Unfortunately you can't mix it with member function pointers to other classes or ordinary function pointers. And you have to specify the object to use at the moment you invoke your function.

Better solution: command pattern

The command pattern allows you more flexibility than function pointers. Thanks to specialization of commands, you can mix commands that will invoke ordinary function pointers, member function pointers of different classes, or ad-hoc functions.

The commands could look like this:

class Command {
public: 
    virtual void execute() = 0; 
    virtual ~Command();
}; 

class CommandOtherClass : public Command { 
    OtherClass *target; 
    void (OtherClass::*f)(); 
public: 
    CommandOtherClass (void (OtherClass::*fct)(), OtherClass*t); 
    void execute() override; 
};

The implementation is really straightforward:

CommandOtherClass::CommandOtherClass (void (OtherClass::*fct)(), OtherClass*t) 
    :  f(fct),target(t) 
{
} 
void CommandOtherClass::execute() {
    (target->*f)();
}

The SomeClass function could be changed as follows:

class SomeClass {
public:
    static std::vector<unique_ptr<Command>> UpdateFuncs;
    static void executeAll();
};

Note that you could develop it so to include a registration and unregistration functions, so that you can also remove from the vector commands involving objects that no longer exist.

Adding a new command to the vector would be done like this:

OtherClass::OtherClass() {
    SomeClass::UpdateFuncs.push_back(make_unique<CommandOtherClass>(&OtherClass::Update, this));
}

Finally, here a complete online demo that invokes two commands automatically registered for some local objects, and even add a third ad hoc command unrelated to any other object.

Upvotes: 0

Christian Hackl
Christian Hackl

Reputation: 27528

OtherClass::Update is not suitable for a void (*)() function pointer, because it's a non-static member function; it's as if it had an "invisible" OtherClass* parameter.

Use std::function to achieve your goal:

#include <functional>

class Time
{
public:
    static std::vector<std::function<void()>> UpdateFuncs;
};

In OtherClass.cpp, use a this-capturing lamba as a function object:

OtherClass::OtherClass()
{
    Time::UpdateFuncs.push_back([this] { Update(); });
}

Of course, if you make Update static, then you can still work with void (*)() if you want to, because the "invisible" parameter is removed, but std::function is just the safe and modern way to do it.

Upvotes: 1

Related Questions