Кому-то - баян, кому-то, возможно, покажется достойным применения. Итак: некоторые библиотеки (боже, как бесит это слово!) работают в поле событийного интерфейса путём назначения коллбэков, простейший пример тому - Wire.onReceive(handler). Этот подход к реализации событий многим известен и многими практикуется, однако, наряду с достоинствами - у этого подхода есть и определённые недостатки, хотя бы тот факт, что указатель на функцию-обработчик надо где-то хранить (а это место в оперативной памяти). В дополнение к этому - существует ещё один вполне себе годный подход, который применяется при объявлении той же функции yield, а именно - позднее связывание. Если совсем короче, то - любой функции можно назначить альяс (синоним, заглушку, если хотите), и если функция не определена - то на стадии линковки все вызовы такой функции заменятся на вызовы заглушки. Что нам это даёт? Как минимум - упрощение кода, вместо: Код (C++): if(onReceiveHandler) onReceiveHandler(bla, bla,bla); Достаточно написать просто Код (C++): onReceiveHandler(bla,bla,bla); Из ограничений, навскидку - тот факт, что таким образом реализованная событийность будет всегда ссылаться на одну функцию, вне зависимости от экземпляра класса. Но и это - не всегда минус, скажем так. Как это работает и как это сделать? Всё просто - объявляете функцию: Код (C++): extern "C" { void MY_RECEIVE(int bytesReceived); } Потом - делаете её альяс: Код (C++): extern "C" { static void __nohandler(int dummy){} } Потом - указываете, что функция - позднего связывания и имеет заглушку: Код (C++): void MY_RECEIVE(int) __attribute__ ((weak, alias("__nohandler"))); Всё - теперь вы в любом месте проекта сможете позвать MY_RECEIVE (например, с параметром 10 - MY_RECEIVE(10); ) - и не париться, куда попадёт вызов: если желающий принимать это событие определит функцию, а именно - напишет в исходном коде: Код (C++): void MY_RECEIVE(int bytesReceived) { Serial.print(F("bytes received: ")); Serial.println(bytesReceived); } то ваш вызов функции MY_RECEIVE попадёт именно в реализованный подписчиком обработчик. Если же функции MY_RECEIVE нигде не определено - её вызов переадресуется на заглушку, а именно - на функцию __nohandler. Что, кстати, даёт нам ещё один инструмент - обработка событий по умолчанию Надеюсь, данная поверхностная заметка будет кому-либо полезна.
Интересно, спасибо. По-моему, главный плюс этого подхода, по сравнению с колбеками - отсуствие необходимости проверять, определена ли функция и отрабатывать ситуацию пустого колбека. А тут все получается автоматически. "Одна функция , независимо от экземпляра класса" - поправь меня. разве нельзя внутри функции вызвать что-то типа _self, чтобы определить, какой экземпляр ее вызвал?
Считаю также плюсом наличие обработчика по умолчанию - та самая заглушка Можно, конечно. Просто достаточно определить прототип события, как надо, например: Код (C++): extern "C" { void ON_RECEIVE(MyClass& sender, int bytesReceived); } И потом просто в классе, который вызывает событие, писать: Код (C++): void MyClass::readFromPort() { .... ON_RECEIVE(*this,10); // говорим подписчикам на событие, что именно мы приняли 10 байт } Просто функция ON_RECEIVE остаётся одна на все экземпляры классов, в отличие от типичных коллбэков, где каждому экземпляру класса можно назначить свой коллбэк: Код (C++): void myHandler1(){} void myHandler2(){} MyClass cl1; MyClass cl2; cl1.onReceive(myHandler1); cl2.onReceive(myHandler2); Короче - применимо и то, и то, всё зависит от задач и контекста Указанным мной способом удобно делать закладки "на будущее", например, периодически из любого кода дёргать что-то там, чтобы не было тормозов, а в обработчике делать это самое "чтобы не было тормозов" (именно для этого авторами обвязки Wiring была предоставлена yield - чтобы "библиотеки" дёргали её в момент длительных операций; даже внутри delay есть вызов yield ).