Home > Blockchain >  Proper handling of member function pointers
Proper handling of member function pointers

Time:12-18

I wrote a generic class for handling and executing a function pointer. This is a simplified equivalent of std::function and std::bind. To handle member functions I use cast to internal EventHandler::Class type. Question: is it ok to cast it that way? Will it work in all cases when invoking handled function?

template <typename ReturnType, typename... Arguments>
class EventHandler
{
    class Class {};
    ReturnType (Class::*memberFunction)(Arguments...) = nullptr;
    union {
        Class *owner;
        ReturnType(*function)(Arguments...) = nullptr;
    };

    public:

        EventHandler() = default;
        EventHandler(EventHandler &&) = default;
        EventHandler(const EventHandler &) = default;
        EventHandler &operator=(EventHandler &&) = default;
        EventHandler &operator=(const EventHandler &) = default;

        EventHandler(ReturnType (*function)(Arguments...)) :
            function(function)
        {
        }

        template <typename Owner>
        EventHandler(Owner *owner, ReturnType (Owner::*memberFunction)(Arguments...)) :
            memberFunction((ReturnType (Class::*)(Arguments...)) memberFunction),
            owner((Class *) owner)
        {
        }

        template <typename Owner>
        EventHandler(const Owner *owner, ReturnType (Owner::*memberFunction)(Arguments...) const) :
            memberFunction((ReturnType (Class::*)(Arguments...)) memberFunction),
            owner((Class *) owner)
        {
        }

        ReturnType operator()(Arguments... arguments)
        {
            return memberFunction ?
                (owner ? (owner->*memberFunction)(arguments...) : ReturnType()) :
                (function ? function(arguments...) : ReturnType());
        }
};

The implementation provides handle for a global function, a member function and a const member function. Obviously there is volatile and const volatile that is not show here for clarity.

EDIT

All the code below is just a representation of all of kinds of supported functions.

class Object
{
    public:

        double y = 1000;
        Object() = default;
        Object(double y) : y(y) {}

        static void s1(void) { std::cout << "s1()" << std::endl; }
        static void s2(int a) { std::cout << "s2(a:" << 10   a << ")" << std::endl; }
        static void s3(int a, float b) { std::cout << "s3(a:" << 10   a << ", b:" << 10   b << ")" << std::endl; }
        static int s4(void) { std::cout << "s4(): "; return 10   4; }
        static Object s5(int a) { std::cout << "s5(a:" << 10   a << "): "; return Object(10   5.1); }
        static float s6(int a, Object b) { std::cout << "s6(a:" << 10   a << ", b:" << 10   b.y << "); "; return 10   6.2f; }

        void m1(void) { std::cout << "m1()" << std::endl; }
        void m2(int a) { std::cout << "m2(a:" << y   a << ")" << std::endl; }
        void m3(int a, float b) { std::cout << "m3(a:" << y   a << ", b:" << y   b << ")" << std::endl; }
        int m4(void) { std::cout << "m4(): "; return ((int) y)   4; }
        Object m5(int a) { std::cout << "m5(a:" << y   a << "): "; return Object(y   5.1); }
        float m6(int a, Object b) { std::cout << "m6(a:" << y   a << ", b:" << y   b.y << "); "; return ((int) y)   6.2f; }

        void c1(void) const { std::cout << "c1()" << std::endl; }
        void c2(int a) const { std::cout << "c2(a:" << y   a << ")" << std::endl; }
        void c3(int a, float b) const { std::cout << "c3(a:" << y   a << ", b:" << y   b << ")" << std::endl; }
        int c4(void) const { std::cout << "c4(): "; return ((int) y)   4; }
        Object c5(int a) const { std::cout << "c5(a:" << y   a << "): "; return Object(y   5.1); }
        float c6(int a, Object b) const { std::cout << "c6(a:" << y   a << ", b:" << y   b.y << "); "; return ((int) y)   6.2f; }
};

void f1(void) { std::cout << "f1()" << std::endl; }
void f2(int a) { std::cout << "f2(a:" << a << ")" << std::endl; }
void f3(int a, float b) { std::cout << "f3(a:" << a << ", b:" << b << ")" << std::endl; }
int f4(void) { std::cout << "f4(): "; return 4; }
Object f5(int a) { std::cout << "f5(a:" << a << "): "; return Object(5.1); }
float f6(int a, Object b) { std::cout << "f6(a:" << a << ", b:" << b.y << "); "; return 6.2f; }

Here is the usage example for all of the above functions

int main()
{
    std::cout << "=== Global functions" << std::endl;
    EventHandler ef1(f1); ef1();
    EventHandler ef2(f2); ef2(2);
    EventHandler ef3(f3); ef3(3, 3.1f);
    EventHandler ef4(f4); std::cout << ef4() << std::endl;
    EventHandler ef5(f5); std::cout << ef5(5).y << std::endl;
    EventHandler ef6(f6); std::cout << ef6(6, Object(6.1)) << std::endl;
    std::cout << std::endl;

    std::cout << "=== Member static functions" << std::endl;
    EventHandler es1(Object::s1); es1();
    EventHandler es2(Object::s2); es2(2);
    EventHandler es3(Object::s3); es3(3, 3.1f);
    EventHandler es4(Object::s4); std::cout << es4() << std::endl;
    EventHandler es5(Object::s5); std::cout << es5(5).y << std::endl;
    EventHandler es6(Object::s6); std::cout << es6(6, Object(6.1)) << std::endl;
    std::cout << std::endl;

    std::cout << "=== Member functions" << std::endl;
    Object object(20);
    EventHandler em1(&object, &Object::m1); em1();
    EventHandler em2(&object, &Object::m2); em2(2);
    EventHandler em3(&object, &Object::m3); em3(3, 3.1f);
    EventHandler em4(&object, &Object::m4); std::cout << em4() << std::endl;
    EventHandler em5(&object, &Object::m5); std::cout << em5(5).y << std::endl;
    EventHandler em6(&object, &Object::m6); std::cout << em6(6, Object(6.1)) << std::endl;
    std::cout << std::endl;

    std::cout << "=== Member const functions" << std::endl;
    const Object constObject(30);
    EventHandler ec1(&constObject, &Object::c1); ec1();
    EventHandler ec2(&constObject, &Object::c2); ec2(2);
    EventHandler ec3(&constObject, &Object::c3); ec3(3, 3.1f);
    EventHandler ec4(&constObject, &Object::c4); std::cout << ec4() << std::endl;
    EventHandler ec5(&constObject, &Object::c5); std::cout << ec5(5).y << std::endl;
    EventHandler ec6(&constObject, &Object::c6); std::cout << ec6(6, Object(6.1)) << std::endl;

    system("pause");
    return 0;
}

Finally - to the point - here an example that shows how much easier in use is the EventHandler I prepared when compared to std::function interface. And actually the reason of such approach.

    EventHandler<float, int, Object> example;

    example = f6;
    example(7, Object(7.1));

    example = EventHandler(&object, &Object::m6);;
    example(8, Object(8.1));

CodePudding user response:

It’s undefined behavior to call a function through a function pointer(-to-member) of a different type. (Some practical reasons for this rule are that the object’s address might need to be adjusted to call a member function of a base class or that a vtable might be involved.) You can use type erasure to allow calling member functions on objects of different types (which is what std::bind does), or you can (restrict to member functions and) add the class type as a template parameter.

Of course, the usual answer is to just use std::function with a lambda that captures the object in question and calls whatever member function. You can also take the C approach and define various functions with a void* parameter that cast that parameter to a known class type and call the desired member function.

  • Related