Передача указателя метода

Я создал небольшую библиотеку, которая принимает функцию обратного вызова:

MyClass::add_callback(bool (*callback_function)(), byte behaviour) {
  // ...
  bool result = callback_function();
  // ...
}

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

Я хотел бы изменить этот метод или добавить другой метод, который принимает метод, а не функцию. Как мне это сделать? Я борюсь с основами Си, но ответы на StackOverflow, похоже, сосредоточены на более современных вариантах Си. Например, этот отличный ответ на Stackoverflow рекомендует использовать boost::bind, но я скорее не включаю такую большую зависимость.

То, что я ищу, - это что-то вроде:

MyClass::add_callback(void *obj, bool (*callback_method)(), byte behaviour) {
  // ...
  bool result = Object::callback_method(&obj);
  // ...
}

где Object::callback_method(&obj); должен быть эквивалентен Object obj; bool result = obj.callback_method();.

Для бонусных очков можно ли сделать это независимо от класса объектов?

Я рад за указатели на другие сайты (например, StackOverflow), при условии, что он применим к голой программе Arduino.

, 👍0

Обсуждение

На случай, если вам интересно, почему я не использую ISR, я немного новичок в Arduino и C. Мое оборудование-это ftDuino и [библиотека Ftduino](https://github.com/harbaum/ftduino/tree/master/ftduino/libraries/Ftduino) использует таймеры и прерывания на ATmega32U4, и я хочу пока избежать конфликтов., @MacFreek


1 ответ


Лучший ответ:

3

Самый простой способ, который я знаю, это использовать оболочку в стиле "C"для вызова функции-члена. На самом деле это делается для сопряжения обратных вызовов C с++ объектами.

Вы могли бы сделать это так:

struct Blinky {
  //state etc.
  bool isOn() { return /* something useful */ true; }
};

//  Оболочка "C" для обратного
bool Blinky_callback(void *vblinky)
{
  return static_cast<Blinky*>(vblinky)->isOn();
}

struct MyClass {
  void add_callback(void *obj, bool (*callback_method)(void *), byte behaviour) {
    // ...
    bool result = callback_method(obj);
    // ...
  }
};

// Globals
MyClass c;
Blinky b;

void setup() {
  c.add_callback(&b, Blinky_callback, 0);
}

Немного более аккуратный способ со статическим членом (функционально идентичным):

struct Blinky {
  //state etc.
  bool isOn() { return /* something useful */ true; }

  static bool callback(void *vblinky) {
    return static_cast<Blinky*>(vblinky)->isOn();
  }
};

// ...
c.add_callback(&b, Blinky::callback, 0);

Другие варианты будут использовать наследование и виртуальные функции.

// Абстрактный базовый класс
struct Sensor {
  virtual bool do_check() = 0;
};

struct Blinky : public Sensor {
  bool do_check() override { return true; }
}

Тогда вы можете сделать так, чтобы ваш add_callback выглядел следующим образом:

void MyClass::add_sensor(Sensor *sensor) {
  // ...
  bool result = sensor->do_check();
  // ...
}

Это относительно приятно для кода, и у компилятора есть шанс сохранить ваш код типобезопасным, если все "вещи", которыми вы хотите управлять, могут соответствовать одному и тому же интерфейсу. Виртуальная отправка будет стоить не намного больше (если вообще что-то), чем статическая оболочка в предыдущем примере. Однако вы "платите" несколько байтов за класс и один указатель на экземпляр виртуальной таблицы.

Более современные методы будут включать шаблоны и/или лямбды, или , возможно, std::function, но все это кажется излишним, если ваша потребность такова, как описано (и может генерировать гораздо больше кода, чем вы ожидаете, или делать динамические выделения для std::function, которых лучше избегать в контексте).


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

void add_callback(Blinky *obj, bool (Blinky::*callback_method)(), byte behaviour) {
  // ...
  bool result = (obj->*callback_method)();
  // ...
}

И ты привязан к этому классу. Если только вы не сделаете add_callback шаблоном, но остерегайтесь дополнительного codegen.

,

Спасибо за такой подробный ответ так быстро. Я посмотрю и отмечу, как прочитано, как только полностью пойму это. По крайней мере, это даст мне несколько хороших советов. Мне кажется, я немного борюсь с тем, какой уровень абстракции использовать для микроконтроллеров. Возвращаясь к C после довольно долгого общения с Python и друзьями, интересно посмотреть, насколько плохо управление памятью C и насколько слаба защита экземпляров Python. Ваш ответ помогает мне понять, какого рода самоанализа я могу ожидать от C, особенно на платформе Arduino с небольшим количеством библиотек высокого уровня., @MacFreek

@MacFreek: ни один код, который я опубликовал (или который вы опубликовали), не является C. Все это на языке Си++. (И C++ - это не современный C, это два разных языка). Посмотрите в разделе "RAII", как вам следует справляться с управлением памятью/временем жизни объектов. Многое из того, что вы найдете, не очень подходит для небольших встроенных проектов, но основная идея важна. (Хотя аббревиатура-отстой.), @Mat

Ты прав. Я использовал "C", потому что мой код Arduino в основном использует простые функции и глобалы, а не классы (что хорошо для этой цели)., @MacFreek

Я не уверен, что это синтаксис C или C++ или исторические/современные конструкции, но я отметил, что Arduino IDE допускает архаичные конструкции. Например, вы можете исключить "это ->" в методах или исключить префикс " std::` для определений типов. Я нашел трудный путь, когда "задержка ()" в моем методе вызывала не функцию Arduino, а " это->задержка ()". Был бы способ сделать IDE более строгой и разрешать только рекомендуемые конструкции, чтобы я учился только правильно?, @MacFreek

Спасибо за ваши примеры кода. Теперь я понимаю, что мне нужна функция-оболочка, предпочтительно в классе с сохранением состояния с вызываемым методом. Я пытался решить эту проблему в вызывающем экземпляре, без оболочки., @MacFreek

Я не знаю, можете ли вы изменить параметры компилятора, используемые IDE (использует "- std=gnu++11-fpermissive", что не является супер строгим), на этом сайте уже могут возникнуть вопросы по этому поводу. Отказ от "этого" всякий раз, когда вы можете, является стандартной практикой, вам это редко нужно. Отсутствующий префикс "std::" должен быть ошибкой компиляции, но есть несколько способов ввести имя в область видимости., @Mat

В вашем ответе вместо "классов" используется "структура". Это рекомендуется или личные предпочтения?, @MacFreek

Единственное различие между ними заключается в том, что "структура" по умолчанию предоставляет публичный доступ, тогда как "класс" предоставляет частный. Таким образом, "структура" проще для простых примеров. Остальное-вопрос стиля/предпочтений., @Mat