Указатели функции обратного вызова С++ с/без классов
Я застрял. Я пытаюсь создать функцию, которая будет использовать бесклассовые указатели функций и объекты из объектов. Вот мой текущий код, который, надеюсь, объясняет больше.
(Он должен работать на 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
Ответы
Ответ 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).
Указанные библиотеки предназначены только для заголовков, поэтому они, вероятно, не считаются "большими библиотеками".