Подтвердить что ты не робот

Указатели функции обратного вызова С++ с/без классов

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

(Он должен работать на Arduino, поэтому я не могу использовать большие библиотеки.)

Во-первых, я использую эту библиотеку для Arduino:

/* SimpleTimer - A timer library for Arduino.
 * Author: [email protected]
 * Copyright (c) 2010 OTTOTECNICA Italy
 */

Что берет функции, которые он вызывает на заданном интервале таймера этого типа:

typedef void (*timer_callback)(void);

Насколько мне известно, это функция классов, веб-страница Указатели на функции-члены завела меня очень далеко, но недостаточно. Вероятно, дефицит терминологии на моей стороне.

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

Итак, есть класс Robot, который имеет Robot::halt(). Я хочу, чтобы робот двигался вперед в течение определенного времени. Например:

void Robot::forward(int speed, long time) {
    reset();
    timer.setTimer(time, c_func, 1);

    analogWrite(l_a, speed);
    analogWrite(r_a, speed);
    isMoving(true);
}

void Robot::halt() {
    __isMoving = false;
    digitalWrite(r_a, LOW);
    digitalWrite(r_b, LOW);
    digitalWrite(l_b, LOW);
    digitalWrite(l_a, LOW);
}

Переменная c_func является бесклассовой функцией в этой точке, но я хотел бы использовать функцию Robot::halt. Я посмотрел, прочитал, узнал, но еще не успел. Я просто не могу обернуть голову вокруг этого, потому что мне не хватает угла.

Я пробовал:

timer.setTimer(time, (this->*halt), 1);
timer.setTimer(time, Robot::*halt, 1);
timer.setTimer(time, &Robot::halt), 1);

Но это все равно будет той же проблемой/меня просто колоть в темноте здесь...

ИЗМЕНИТЬ

Ранее я сказал, что не хочу менять код библиотеки SimpleTimer. Я хочу вернуться к этому, я думаю, что изменить его там будет лучший вариант.

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

Чтобы продолжить, измените код SimpleTimer. Этот класс должен иметь ссылку на объект, который содержит мою функцию "halt", правильно? Таким образом, перегрузка функции привязки к чему-то, что принимает мой объект, и моя функция как два отдельных указателя, будут работать...? Я думаю, что получаю это от этого, но я еще не с головой.

ИЗМЕНИТЬ

Я не знаю, кто пришел с этим снова, но кто-нибудь нашел эту нить. Если найдено указатели на функции и самые быстрые делегаты С++, вы получите очень хорошее введение в указатели функций и указатели функций-членов.

ИЗМЕНИТЬ

Сработал, изменил библиотеку SimpleTimer, чтобы использовать эту систему Delegate: http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Он очень хорошо интегрировался, и было бы неплохо иметь стандартную систему делегатов, подобную этой, в библиотеке Arduino.

Код, как в тесте (рабочий)

ЬурейеЕ

typedef FastDelegate0<> FuncDelegate;

Код в классе робота:

void Robot::test(){
    FuncDelegate f_delegate;
    f_delegate = MakeDelegate(this, &Robot::halt);

    timer.setTimerDelg(1, f_delegate, 1);
}

void Robot::halt() {
    Serial.println("TEST");
}

Код в классе SimpleTimer:

int SimpleTimer::setTimerDelg(long d, FuncDelegate f, int n){
    f();
}

Arduino печатает TEST в консоли.

Следующий шаг, помещая его в массив, не видит там много проблем. Спасибо всем, я не могу поверить тому, чему научился через два дня.

Какой запах? Это запах...? Успех!

Для заинтересованных пользователей используемая система Delegate не связана с проблемами емкости памяти: С FastDelegate

AVR Memory Usage
----------------
Device: atmega2560

Program:   17178 bytes (6.6% Full)
(.text + .data + .bootloader)

Data:       1292 bytes (15.8% Full)
(.data + .bss + .noinit)


Finished building: sizedummy

Без FastDelegate:

AVR Memory Usage
----------------
Device: atmega2560

Program:   17030 bytes (6.5% Full)
(.text + .data + .bootloader)

Data:       1292 bytes (15.8% Full)
(.data + .bss + .noinit)


Finished building: sizedummy
4b9b3361

Ответ 1

Вы можете сделать это, создав functor объект, который действует как прокси-сервер между кодом таймера и вашим кодом.

class MyHaltStruct
{
public:
    MyHaltStruct(Robot &robot)
        : m_robot(robot)
        { }

    void operator()()
        { robot.halt(); }

private:
    Robot &m_robot;
}

// ...

timer.setTimer(time, MyHaltStruct(*this), 1);

Изменить

Если это невозможно сделать с помощью объекта-функтора, вместо этого вы могли бы использовать глобальные переменные и функции, возможно, в пространстве имен:

namespace my_robot_halter
{
    Robot *robot = 0;

    void halt()
    {
        if (robot)
            robot->halt();
    }
}

// ...

my_robot_halter::robot = this;
timer.setTimer(time, my_robot_halter::halt, 1);

Это работает, только если у вас есть один экземпляр робота.

Ответ 2

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

Robot *global_robot_for_timer;
void robot_halt_callback()
{
    global_robot_for_timer->halt();
}

вы можете по крайней мере обернуть эту лот в свой собственный файл, но это не очень. Как предположил Мэтью Мердок, было бы лучше отредактировать сам SimpleTimer. Более обычным интерфейсом будет:

typedef void (*timer_callback)(void *);

SimpleTimer::setTimer(long time, timer_callback f, void *data);

void robot_halt_callback(void *data)
{
    Robot *r = (Robot *)data;
    r->halt();
}

т.е., когда вы вызываете setTimer, вы предоставляете аргумент, который передается обратно на обратный вызов.

Наименьшее изменение в SimpleTimer было бы следующим:

SimpleTimer.h

typedef void (*timer_function)(void *);
struct timer_callback {
    timer_function func;
    void *arg;
};
// ... every method taking a callback should look like this:
int SimpleTimer::setTimeout(long, timer_function, void *);

SimpleTimer.cpp

// ... callbacks is now an array of structures
callbacks[i] = {0};

// ... findFirstFreeSlot
if (callbacks[i].func == 0) {

// ... SimpleTimer::setTimer can take the timer_callback structure, but
// that means it callers have to construct it ...
int SimpleTimer::setTimeout(long d, timer_function func, void *arg) {
    timer_callback cb = {func, arg};
    return setTimer(d, cb, RUN_ONCE);
}

Ответ 3

Вы не можете передать нестационарную функцию-член там - только статическую. Подпись должна быть такой:

static void halt()
{
    //implementation
}

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

Так что в вашей реализации

void halt();

действует

static void halt( Robot* thisPointer );

и когда вы делаете

void Robot::halt() {
    __isMoving = false;
}

у вас есть это:

void Robot::halt( Robot* thisPointer ) {
    thisPointer->__isMoving = false;
}

и, конечно, указатель функции halt( Robot*) не может быть передан вместо функции обратного вызова void (*)(void) C.

И да, если вам нужен доступ к нестационарным переменным-членам из class Robot изнутри обратного вызова, вам придется каким-то образом получить указатель на экземпляр class Robot в другом месте - например, сохраните его как статическую переменную-член так что вы не полагаетесь на указатель this.

Ответ 4

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

Как говорит текущий ответ, лучше всего пойти с функторами. Хотя функция setTimer может принимать только указатели на функции, можно написать функцию шаблона, чтобы обернуть вызов и принять оба. Для еще более мелкозернистой обработки вы можете написать метапрограмму шаблона (Boost.TypeTraits имеет is_pointer, is_function и даже is_member_function_pointer) для обработки различных случаев.

Как вы делаете функторы - это совсем другая история. Вы можете выбрать для написания их вручную (что означает внедрение класса с operator() для каждого из них), но в зависимости от ваших потребностей, которые могут быть утомительными. Пара вариантов:

  • std::bind: вы можете использовать его для создания функтора, первый параметр которого будет привязан к указанному вами значению - в случае функций-членов он будет экземпляром.
  • В зависимости от вашего компилятора у вас может не быть доступа к std::bind - в этом случае я предлагаю boost::bind. Это библиотека только для заголовков и обеспечивает ту же функциональность.
  • Вы можете использовать другую реализацию делегата. У меня нет опыта с этим, но он утверждает, что он быстрее других реализаций (включая std:: function).

Указанные библиотеки предназначены только для заголовков, поэтому они, вероятно, не считаются "большими библиотеками".