Передать метод класса как указатель на пустую функцию (C ++ 11) - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть объект, который должен взаимодействовать с существующим C api, чтобы зарегистрировать прерывание (функция void не принимает аргументов). Я могу прикрепить прерывание к функции function(). Однако я хочу иметь возможность передавать аргументы функции, но это изменило бы сигнатуру функции. Я подумал, что можно создать объект для хранения параметров (и изменить их при необходимости), а затем передать метод (или аналогичный). Однако я не смог понять, как это сделать.

Я пытался передать лямбду как [=](){ std::cout << "a: " << a << "\n"; }, но оказалось, что лямбда-символы с захватом не могут быть преобразованы в указатели функций. Я также попробовал шаблонный метод (так как он будет создан во время компиляции), но не смог заставить его работать. Я видел некоторые сообщения о SO, в которых говорится о std::bind и std::function, но они часто предупреждают о накладных расходах виртуальных функций, которых я бы хотел избежать на встроенной платформе для ISR.

Как лучше всего преобразовать параметризованную функцию в 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;
}

РЕДАКТИРОВАТЬ: Мое решение, вдохновленное выбранным ответом:

#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;
}

Ответы [ 2 ]

0 голосов
/ 15 ноября 2018

Ну вот запутанный способ сделать это. Это требует некоторого стандартного кода, так что я завернул его в пару MACROS (хм). Для C++11 блокировка несколько ограничена (читайте менее эффективно), но это можно улучшить, если у вас есть доступ к C++14 или выше:

// ## 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...
}

Как уже отмечалось, если у вас есть C++14, вы можете заменить мьютекс и код блокировки на более эффективные функции:

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()); }
0 голосов
/ 15 ноября 2018

Итак, вы хотите зарегистрировать один и тот же обработчик прерываний для разных прерываний, каждое из которых имеет одинаковые, но индивидуальные данные ...

Как насчет автономной функции шаблона со статическими данными?

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

void(*interruptVectorTable[12])() =
{
   // ...
   &irqHandler<7>,
   // ...
   &irqHandler<10>,
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...