Обратный вызов родительского класса из дочернего класса
Я хочу, чтобы родительский класс передал указатель на функцию обратного вызова дочернему классу при создании дочернего класса. У меня это получилось, когда родителем был скетч, благодаря этому сообщению:
В этом примере родителем был файл .ino
, т.е. не класс, а набор функций. Этот код работал нормально.
Однако мне нужно иметь возможность вызывать дочерний конструктор из класса, как показано в приведенном ниже коде. Я получаю "недопустимое использование нестатической функции-члена в строке child = new Child (handlerDispatcher);
Вот полный код:
class Child {
public:
typedef void ( *callback_t ) ( uint8_t, uint8_t );
callback_t callback;
// Конструктор
Child ( callback_t _callback ): callback ( _callback ) {}
void doCallback ( uint8_t id, uint8_t index ) {
callback ( id, index );
}
void makeCallbacks () {
doCallback ( 1, 0 );
doCallback ( 2, 1 );
doCallback ( 3, 2 );
}
};
class Parent {
public:
Child *child;
Parent () {}
void handlerDispatcher ( uint8_t id, uint8_t index ) {
( this->*callbackHandlers [ index ] ) ( id );
}
typedef void ( Parent:: *handlerPointer ) ( uint8_t );
handlerPointer callbackHandlers [ 3 ] = {
&Parent::handler1,
&Parent::handler2,
&Parent::handler3,
};
void handler1 ( uint8_t index ) { echo ( index ); }
void handler2 ( uint8_t index ) { echo ( index ); }
void handler3 ( uint8_t index ) { echo ( index ); }
void echo ( uint8_t value ) {
Serial.print ( "Got: " );
Serial.println ( value );
}
void configure () {
child = new Child ( handlerDispatcher );
}
void test () {
child->makeCallbacks ();
}
};
Parent parent;
void setup () {
Serial.begin ( 115200 );
parent.configure ();
parent.test ();
}
void loop() {}
@Bob Jones, 👍0
2 ответа
Это потому, что
void Parent::handlerDispatcher ( uint8_t id, uint8_t index )
не
void ( *callback_t ) ( uint8_t, uint8_t )
Ближайшим эквивалентом будет:
void ( *callback_t ) (Parent *, uint8_t, uint8_t )
Хотя я не уверен, что это вообще возможно.
Но, похоже, вы пытаетесь сделать что-то безумно запутанное и сложное, что было бы намного проще, если бы вы просто использовали наследование объектов и забыли об обратных вызовах.
При наследовании объектов дочерний объект может реализовывать функции, определенные родительским объектом. Таким образом, вы получите один экземпляр объекта, представляющий собой комбинацию родительских и дочерних функций, одна из которых накладывается на другую.
Чтобы развить превосходный ответ Джурай и добавить немного пояснений, если вы возьмете следующий родительский объект:
class Parent {
public:
virtual void fnc1() = 0;
void fnc2 {
fnc1();
}
};
Здесь у нас есть две родительские функции. fnc2()
просто вызывает fnc1()
в качестве примера. Однако fnc1()
не является обычной функцией — это чисто виртуальная функция. Это похоже на interface
в Java. Это заполнитель в родительском элементе, который говорит: «Эта функция будет существовать, но ее будет реализовывать дочерний элемент, а не я». Если вы попытаетесь создать экземпляр родительского объекта, компилятор сообщит, что некоторые функции являются чисто виртуальными и объект не может быть создан.
Итак, вы создаете дочерний класс, который берет родительский класс и расширяет его телом любых необходимых чисто виртуальных функций, а также может добавлять другие. Например:
class Child : public Parent {
public:
void fnc1() final {
...
}
};
Здесь у нас есть только одна функция - fnc1()
, которая помечена как final
. Ключевое слово final
в некотором роде противоположно ключевому слову virtual
. virtual
означает "Эта функция будет переопределена дочерним элементом". final
означает «Я переопределил это, и теперь ничто другое не может переопределить». Это не обязательно, но несколько помогает оптимизатору компилятора, поскольку ему больше не нужно беспокоиться о дальнейшем наследовании этой функции.
Теперь вы можете создать экземпляр Child
, и у вас будет доступ ко всем родительским функциям и данным, а также ко всем дочерним функциям и данным. Простой вызов fnc2()
в этом дочернем экземпляре приведет к вызову этой функции в родительском экземпляре, который затем сам вызовет fnc1()
, реализованный дочерним элементом.
И благодаря тому, что называется полиморфизмом, дочерний элемент можно рассматривать как родителя во всех смыслах и целях (хотя, если вы посмотрите на него как на родителя, вы не увидите никаких собственных дочерних элементов). функции).
В заключение приведем полный пример, в котором используются два разных дочерних класса и сохраняются указатели на них в массиве родительских элементов (поэтому дочерние элементы рассматриваются так, как если бы они были объектами класса Parent
). ), перебирает их и вызывает родительскую функцию fnc2()
:
class Parent {
public:
virtual void fnc1() = 0;
void fnc2() {
fnc1();
}
};
class Child1 : public Parent {
public:
void fnc1() final {
Serial.println("I am child 1");
}
};
class Child2 : public Parent {
public:
void fnc1() final {
Serial.println("I am child 2");
}
};
Child1 c1;
Child2 c2;
Parent *objects[2] = {
&c1, &c2
};
void setup() {
Serial.begin(115200);
for (int i = 0; i < 2; i++) {
objects[i]->fnc2();
}
}
void loop() { }
Последовательный вывод этой программы:
I am child 1
I am child 2
Я думаю, это и есть тот конечный эффект, который вам нужен.
Используйте виртуальные функции. Для объекта типа Child
fnc2
, определенный в Parent
, вызовет fnc1
класса Child
:
class Parent {
public:
virtual void fnc1() {
}
void fnc2 {
...
fnc1();
...
}
}
class Child : public Parent {
public:
virtual void fnc1() {
...
}
}
- Передача нестатической функции-члена с помощью bind
- Вызов функций одного класса из другого класса — Обратный вызов
- Использовать обратные вызовы в ардуино с указателями на функции (будут ли функции сохраняться в памяти?)
- Массив функций
- Как написать эффективные функции обратного вызова на Teensy 4.0?
- C++ против языка Arduino?
- Как использовать SPI на Arduino?
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
Для ребенка отбросьте «виртуальный» и добавьте «конечный», если только вы не хотите иметь внуков. Также вы можете сделать fnc1() родителя *чисто виртуальным*., @Majenko
@Majenko, а то мне пришлось бы объяснять :-), @Juraj
Я расширил свой ответ, чтобы сделать именно это., @Majenko