Указатели на функцию обратного вызова C ++ с / без классов - PullRequest
12 голосов
/ 11 января 2012

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

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

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

/* SimpleTimer - A timer library for Arduino.
 * Author: mromani@ottotecnica.com
 * Copyright (c) 2010 OTTOTECNICA Italy
 */

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

typedef void (*timer_callback)(void);

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

Теперь я создал свой собственный класс, который я хотел бы, в свою очередь, использовать этой библиотекой 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);

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

EDIT

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

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

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

EDIT

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

EDIT

Получил работу, изменил библиотеку SimpleTimer для использования этой системы делегатов: 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 в консоли.

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

Что это за запах? Это запах ...? Успех!

Для заинтересованных лиц используемая система делегатов не составляет проблем с объемом памяти: С 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

Ответы [ 4 ]

8 голосов
/ 11 января 2012

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

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);

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

3 голосов
/ 11 января 2012

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

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's 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);
}
2 голосов
/ 11 января 2012

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

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.

1 голос
/ 11 января 2012

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

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

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

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

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

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