Передать функцию-член в качестве обратного вызова в конструктор другого класса - PullRequest
0 голосов
/ 17 ноября 2018

Привет, я хочу передать функцию-член в качестве аргумента в конструктор другого класса.Я пробовал много решений, но все, что мне дали, провалилисьИтак .. Первый класс ..

class WebSocketsServerRunner : public WebSocketsServer {
   private:
       ThreadController<uint8_t> threads = ThreadController<uint8_t>();
       SensorsState sensorsState;

    void notifyClient(uint8_t clientNumber) { // this is the callback
        String message = sensorsState.readAsJSON();
        WebSocketsServer::sendTXT(clientNumber, message);
    }

    void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {

        // solution bellow doesn't work!
        Thread<uint8_t>* thread = new Thread<uint8_t>(static_cast<int>(num),  &WebSocketsServerRunner::notifyClient, num, 5000);

        threads.add(thread);

    } 
};

И объявление конструктора потока:

    Thread(int _id, void (*callback)(void) = NULL, long _interval = 0);
    Thread(int _id, void (*callback)(Arg), Arg arg, long _interval = 0);

Где Arg template <typename Arg>

Я попробовал все .. std ::bind, & CLASS_NAME :: METHOD_NAME, static_cast, но ничего не работает ..

Компиляция дает мне:

no known conversion for argument 2 from 'void (WebSocketsServerRunner::*)(uint8_t) {aka void (WebSocketsServerRunner::*)(unsigned char)}' to 'void (*)(unsigned char)'

...

no matching function for call to 'Thread<unsigned char>::Thread(int, void (WebSocketsServerRunner::*)(uint8_t), uint8_t&, int)'

1 Ответ

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

Указатели на функции-члены имеют синтаксис, отличный от обычных указателей на функции:

void(*function)();                  // pointer to function
void(YourClass::*memberFunction)(); // pointer to member function

Причина заключается в том, что функции-члены имеют дополнительный скрытый / прозрачный параметр, принимающий объект, для которого вызывается функция, и который представляетсявы как this указатель.

Таким образом, вызов функции-члена также отличается (вам нужен объект для вызова функции):

void(YourClass::*memberFunction)() = &YourClass::someFunction;
YourClass instance;
YourClass* pointer = &instance;
(instance.*memberFunction)();
(pointer->*memberFunction)();

Помните, что и .*, и->* операторы имеют более низкий приоритет, чем оператор вызова функции () (если бы это было хорошее решение, можно было бы поспорить, но это так, как есть ...), поэтому вам нужно круглые скобки.

Ниже я покажу только для иллюстрации природы функций-членов, учтите, что это на самом деле неопределенное поведение (но, скорее всего, это сработает) - не когда-либо используйте это в производительном коде :

void(*functionR)(YourClass&)
    = reinterpret_cast<&void(*)(YourClass&)(&YourClass::someFunction);
void(*functionP)(YourClass*)
    = reinterpret_cast<&void(*)(YourClass*)(&YourClass::someFunction);
YourClass instance;
YourClass* pointer = &instance;
functionR(instance);
functionP(pointer);

Интересным аспектом является то, что и ссылка, и указатель должны делать свое дело, открываяпод капотами ссылки - если они вообще создаются - являются не более чем указателями, исключительно с применением конкретных правил (так, в конце концов, синтаксический сахар).

Выше также видно, чтовызовите функцию-член, даже если вы незаконно используете обычный указатель на функцию, вам все еще нужен объект, для которого вы можете вызвать функцию!

Итак, какие есть варианты?

*Функции-члены 1035 *static не имеют прозрачного параметра функции this, то есть они могут использоваться как любая обычная функция:
static void notifyClient(uint8_t clientNumber)
// ^^^ (!)
{
     WebSocketsServerRunner& instance = selectById(clientNumber);
     String message = instance.sensorsState.readAsJSON();
     WebSocketsServer::sendTXT(clientNumber, message);
}

Если вы не можете получить экземпляр, используя номер клиента, выПридется изменить класс Thread так, чтобы он также содержал объект для вызова функции.

Most generic , не обязательно самый безопасный, хотя используетvoid* указатель:

class Thread
{
    void(*function)(void*);
    void* parameter;

     void run()
     {
         function(parameter);
     }
};

Теперь вы можете использовать статический член и обычные функции, но вам нужно привести параметр обратно, чтобы получить экземпляр, кроме того, вы не можете использоватьумные указатели для безопасного управления памятью.Лучший подход:

template <typename T> 
class Thread
{
    std::function<void(T*)>;
    std::shared_ptr<T> instance;

     void run()
     {
         function(instance.get());
     }
public:
     template <typename F>
     Thread(F function, std::shared_ptr<T>& instance)
         : function(function), instance(instance)
     { }
};

Интересно то, что вы можете хранить как указатель на функцию-член, так и отдельно стоящие функции в объекте std :: function.Другой вариант:

template <typename T> 
class Thread
{
    T t;

     void run()
     {
         t();
     }
public:
     Thread(T&& t)
         : t(std::move(t))
     { }
};

Этот код можно кормить лямбдами:

Thread t([&instance]() { instance.doSomething(); });

Возможно, это будет интересная альтернатива прямому использованию std::thread:

class WebSocketsServerRunner
{
public:
    static void run(uint8_t clientNumber)
    {
        WebSocketsServerRunner instance;
        for(;;)
        {
            // do whatever your Thread class did
        }
    }
};

std::thread t10(&WebSocketsServerRunner::run, 10U);
std::thread t12(&WebSocketsServerRunner::run, 12U);

Редактировать: по вашим комментариям не подходит ...

...