user5255670
user5255670

Reputation:

C++: How to store various member functions of different classes for later use

thanks in advance for your support.

I'm using C++11 and I want to store public member functions of some classes for later use as callback functions; e.g. I want to store some functions that matches this template: void(classname::*)(void). As far as I know, I have to store their objects too, It's fine. For example:

// PSEUDO CODE
class A {
    public:
    void myfunc() {}
}myobj;

class B {
    public:
    void myfunc2() {}
}myobj2;

/* storing */
mystorageclass storage;
storage.push(&myobj, &A::myfunc);
storage.push(&myobj2, &B::myfunc2);

/* call them back */
(storage[0].object->*(storage[0].callback))();
(storage[1].object->*(storage[1].callback))();

Is there any safe and generic way to do that? Actually I've found a way, but I'm not sure how much it's portable across processors or compilers.

//test.cpp - compiled with: g++ test.cpp -o test -std=c++11
#include <iostream>
#include <vector>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc()" << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2()" << std::endl; }
}myobj2;

struct Callback {
    void* object;
    void(* method)(void*);
};

std::vector<Callback> callbackList;

template<typename FunctionPtr>
void add(void* object, FunctionPtr fptr) {
    Callback cb;
    cb.object = object;
    cb.method = (void(*)(void*))(*(void**)(&fptr));
    callbackList.push_back(cb);
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    //call them back
    callbackList[0].method(callbackList[0].object);
    callbackList[1].method(callbackList[1].object);
}

And another way to do; I feel this is much more safe:

//test2.cpp - compiled with: g++ test2.cpp -o test2 -std=c++11
#include <iostream>
#include <vector>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc()" << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2()" << std::endl; }
}myobj2;

struct Callback {
    struct A;
    A* object;
    void(A::* method)();
    void call() {
        (object->*method)();
    }
};

std::vector<Callback> callbackList;

template<typename FunctionPtr>
void add(void* object, FunctionPtr fptr) {
    Callback cb;
    cb.object = (Callback::A*)object;
    cb.method = (void(Callback::A::*)())(fptr);
    callbackList.push_back(cb);
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    //call them back
    callbackList[0].call();
    callbackList[1].call();
}

Does these usages are safe? Or what do you suggest instead of these. Thanks.

Upvotes: 2

Views: 2047

Answers (3)

R Sahu
R Sahu

Reputation: 206577

Problem with the second version

In the line

cb.method = (void(*)(void*))(*(void**)(&fptr));

you are casting a function pointer to void**. I am not sure that is supported by the standard. My guess is it is not. I know casting a function pointer to void* is not supported by the standard. See Print an address of function in C++, g++/clang++ vs vc++ , who is rght? for details.

And then, you proceed to use:

callbackList[1].method(callbackList[1].object);

This relies on conventions used by a compiler to pass this as the first hidden argument when calling a member function of a class. There is no guarantee that the method is used by all compilers. The standard does not explicitly state that.

Problem with the third/last version

You are using:

cb.object = (Callback::A*)object;
cb.method = (void(Callback::A::*)())(fptr);

regardless of whether the object type is A or B. This is cause for undefined behavior. The standard does not support casting of an object pointer to any old pointer type.

A Cleaner Version

Use a base class for Callback.

struct Callback {
   virtual ~Callback() = 0;
   virtual void call() = 0;
};

Then, use a class template for the real Callbacks.

template <typename T>
struct RealCallback : public Callback
{
   RealCallback(T* obj, void (T::*m)(void)) : object(obj), method(m) {}

   virtual void call()
   {
      (object->*method)();
   }

   T* object;
   void (T::*method)();
};

With this, you won't be able to store a list of Callback objects but you can store a list of shared_ptr<Callback>s.

std::vector<std::shared_ptr<Callback>> callbackList;

Here's a complete program that does not rely on any ugly casts and works perfectly.

//test.cpp - compiled with: g++ test.cpp -o test -std=c++11
#include <iostream>
#include <vector>
#include <memory>

class A {
    public:
    void myfunc() { std::cout << "Test A::myfunc() on " << this << std::endl; }
}myobj;

class B {
    public:
    void myfunc2() { std::cout << "Test B::myfunc2() on " << this << std::endl; }
}myobj2;

struct Callback {
   virtual void call() = 0;
};

template <typename T>
struct RealCallback : public Callback
{
   RealCallback(T* obj, void (T::*m)(void)) : object(obj), method(m) {}

   virtual void call()
   {
      (object->*method)();
   }

   T* object;
   void (T::*method)();
};

std::vector<std::shared_ptr<Callback>> callbackList;

template<typename T>
void add(T* object, void (T::*fptr)()) {
    RealCallback<T>* cb = new RealCallback<T>(object, fptr);
    callbackList.push_back(std::shared_ptr<Callback>(cb));
}

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    std::cout << "myobj: " << &myobj << std::endl;
    std::cout << "myobj2: " << &myobj2 << std::endl;

    //call them back
    callbackList[0]->call();
    callbackList[1]->call();
}

Update, in response to comment by Yakk

I think Yakk's suggestion makes sense. You can remove the classes Callback and RealCallback with

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

std::vector<Callback> callbackList;

Then, add can be simplified to:

template<class T>
void add(T* object, void(T::*ptr)()) {
   callbackList.push_back([object, ptr]{ (object->*ptr)();});
}

With those changes, main needs to be slightly updated to:

int main() {
    //add to list for later use
    add(&myobj, &A::myfunc);
    add(&myobj2, &B::myfunc2);

    std::cout << "myobj: " << &myobj << std::endl;
    std::cout << "myobj2: " << &myobj2 << std::endl;

    // Updated. Can't use callbackList[0]->call();

    //call them back
    callbackList[0]();
    callbackList[1]();
}

Upvotes: 2

Jorge Omar Medra
Jorge Omar Medra

Reputation: 988

Try with std::function or std::bindboth of them need to keep the reference to the instance:

#include <string>
#include <iostream>
#include <functional>

using namespace std;
class MyClass
{
    int _value;
public:
    MyClass(int value)
    {
        _value = value;
    }

    void food()
    {
        cout << "Foo is doing something whit value: " << _value << endl;
    }
    void bar()
    {
        cout << "Bar is doing something whit value: " << _value << endl;
    }
};


int main()
{

    MyClass* c1 = new MyClass(1);
    MyClass* c2 = new MyClass(2);

    cout << "Using 'std::function':" << endl;

    std::function<void(MyClass&)> food = &MyClass::food;
    std::function<void(MyClass&)> bar = &MyClass::bar;

    food(*c1);
    bar(*c1);
    food(*c2);
    bar(*c2);


    cout << "Using 'std::bind':" << endl;

    auto foodBind = std::bind(&MyClass::food, std::placeholders::_1);
    auto barBind = std::bind(&MyClass::bar, std::placeholders::_1);

    foodBind(*c1);
    barBind(*c1);
    foodBind(*c2);
    barBind(*c2);

    system("PAUSE");
};

the Output is:

enter image description here

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

Replace Callback with std::function<void()>.

Replace add with

template<class T, class R, class U>
void add(T* object, R(U::*ptr)()) {
  Callback cb = [object, ptr]{ object->ptr(); };
  callbackList.push_back(cb);
  // or just
  // callbackList.push_back([object, ptr]{ object->ptr(); });
}

note that this supports passing in pointers-to-parent member functions, and callbacks that do not return void and discarding the result.

std::function stores a generic "call this later". You pass a type compatible with the return value, and args compatible with what you want to call later, in the template signature argument of std::function<signature>. In this case, <void()>.

Upvotes: 3

Related Questions