Обратный вызов родительского класса из дочернего класса

Я хочу, чтобы родительский класс передал указатель на функцию обратного вызова дочернему классу при создании дочернего класса. У меня это получилось, когда родителем был скетч, благодаря этому сообщению:
В этом примере родителем был файл .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() {}

, 👍0


2 ответа


0

Это потому, что

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

Я думаю, это и есть тот конечный эффект, который вам нужен.

,

1

Используйте виртуальные функции. Для объекта типа Child fnc2, определенный в Parent, вызовет fnc1 класса Child:

class Parent {
public:

  virtual void fnc1() {
  }

  void fnc2 {
    ...
    fnc1();
    ...
  } 
}

class Child : public Parent {
public:
  virtual void fnc1() {
    ...
  }
}
,

Для ребенка отбросьте «виртуальный» и добавьте «конечный», если только вы не хотите иметь внуков. Также вы можете сделать fnc1() родителя *чисто виртуальным*., @Majenko

@Majenko, а то мне пришлось бы объяснять :-), @Juraj

Я расширил свой ответ, чтобы сделать именно это., @Majenko