简体   繁体   中英

Pass class method as void function pointer (C++11)

I have an object which needs to interface with an existing C api to register an in interrupt (void function taking no arguments). I can attach the interrupt to the function function() . However, I want to be able to pass in arguments to the function, but that would change the function signature. I thought a way around that would be to create an object to store the parameters (and modify them as necessary), and then pass in a method (or similar). However, I haven't been able to figure out how to do that.

I've tried passing in a lambda as [=](){ std::cout << "a: " << a << "\\n"; } [=](){ std::cout << "a: " << a << "\\n"; } , but it turns out lambdas with a capture can't be converted to function pointers. I've also tried a templated method (since it would get instantiated at compile time), but couldn't get it to work. I've seen some posts on SO talking about std::bind and std::function , but they often warn about virtual function overhead, which I'd like to avoid on an embedded platform for an ISR.

What is the best way to convert a paramterized function to a void(*)() ?

#include <iostream>

void function() {
    std::cout << "Hello World!\n";
}

void attach_interrupt(void(*fn)()) {
    fn();
}

class A {
    int a;

public:
    A(int a) : a(a) {
        attach_interrupt(function); // This works as expected
        // attach_interrupt(method); // How do I make this work?
        // attach_interrupt(method2<a>);
    }

    void method() {
        // something requiring a and b
        std::cout << "a: " << a << "\n";
    }

    template<int a>
    void method2() {
        std::cout << "a: " << a << "\n";
    }
};

int main()
{
    const int PIN_1 = 0;
    const int PIN_2 = 1;
    const int PIN_3 = 2;

    A foo(PIN_1);
    A bar(PIN_2);
    A baz(PIN_3);

    return 0;
}

EDIT: My solution, inspired by the selected answer:

#include <iostream>

void attach_interrupt(int pin, void(*fn)()) {
    fn();
}

// Free function, which works as an ISR
template <unsigned int IRQ, unsigned int IRQ2>
static void irqHandler()
{
    std::cout << "IRQ: " << IRQ << "\n";
    std::cout << "IRQ2: " << IRQ2 << "\n";
};

template <unsigned int PIN_1, unsigned int PIN_2>
class Handler {
    private:

    public:
        Handler() {
            void(*irq)() = &irqHandler<PIN_1, PIN_2>;
            attach_interrupt(0, irq);
            attach_interrupt(0, &handler_2);
        }

        // static member function can have its address taken, also works as ISR
        static void handler_2() {
            std::cout << "PIN_1: " << PIN_1 << "\n";
            std::cout << "PIN_2: " << PIN_2 << "\n";
        }
};

Handler<1, 2> a;
Handler<2, 3> b;

int main()
{
    return 0;
}

So you want to register one and the same interrupt handler for different interrupts, each having equal, but individual data...

What about a free-standing template function with static data?

template <unsigned int IRQ>
void irqHandler()
{
    static A a(IRQ);
    a.doSomething();
};

void(*interruptVectorTable[12])() =
{
   // ...
   &irqHandler<7>,
   // ...
   &irqHandler<10>,
};

Well here is a convoluted way to do this. It requires some boilerplate code so I wrapped that up in a couple of MACROS (yuck). For C++11 the locking is somewhat limited (read less efficient) but that can be improved upon if you have access to C++14 or above:

// ## Header Library Code

namespace static_dispatch {

inline std::mutex& mutex()
    { static std::mutex mtx; return mtx; }

inline std::lock_guard<std::mutex> lock_for_reading()
    { return std::lock_guard<std::mutex>(mutex()); }

inline std::lock_guard<std::mutex> lock_for_updates()
    { return std::lock_guard<std::mutex>(mutex()); }

inline std::vector<void*>& cbdb()
{
    static std::vector<void*> vps;
    return vps;
}

inline void register_cb(void(*cb)(), void* user_data)
{
    auto lock = lock_for_updates();
    cbdb().push_back(user_data);
    cb(); // assign id under lock
}

inline void* retreive_cb(std::size_t id)
{
    auto lock = lock_for_reading();
    return cbdb()[id];
}

}  // namespace static_dispatch

#define CALLBACK_BOILERPLATE(id) \
    static auto id = std::size_t(-1); \
    if(id == std::size_t(-1)) { id = static_dispatch::cbdb().size() - 1; return; }

#define CALLBACK_RETREIVE_DATA(id, T) \
    reinterpret_cast<T*>(static_dispatch::retreive_cb(id))

// ## Application Code

class A
{
public:
    void member_callback_1() const
    {
        std::cout << s << '\n';
    }

private:
    std::string s = "hello";
};

void callback_1()
{
    CALLBACK_BOILERPLATE(id);

    auto a = CALLBACK_RETREIVE_DATA(id, A);

    a->member_callback_1();
}

// The framework that you need to register your 
// callbacks with
void framework_register(void(*cb)()) { cb(); }

int main()
{
    A a;

    // register callback with data structure
    static_dispatch::register_cb(&callback_1, &a);

    // Now register callback with framework because subsequent calls
    // will invoke the real callback.
    framework_register(&callback_1);

    // etc...
}

As noted about if you have C++14 you can replace the mutex and locking code with the more efficient functions here:

inline std::shared_timed_mutex& mutex()
    { static std::shared_timed_mutex mtx; return mtx; }

inline std::shared_lock<std::shared_timed_mutex> lock_for_reading()
    { return std::shared_lock<std::shared_timed_mutex>(mutex()); }

inline std::unique_lock<std::shared_timed_mutex> lock_for_updates()
    { return std::unique_lock<std::shared_timed_mutex>(mutex()); }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM