Вызов функций одного класса из другого класса — Обратный вызов

Я новичок в C++ & Мне нужно вызвать функции одного класса из другого класса. И чтобы классы ничего не знали друг о друге. Как это сделать в среде Arduino?

class Encoder {
  using CallBack2 = void( * )(const int, const int);
  using CallBack0 = void( * )();
  private:
    CallBack2 callbackUpDown;
    CallBack0 callbackTab;
    CallBack0 callbackLongPress;
  public:
    Encoder() {}

  void Setup(CallBack2 updown, CallBack0 tab, CallBack0 cbLongPress) {
    callbackUpDown = updown;
    callbackTab = tab;
    callbackLongPress = cbLongPress;
  }

  void Loop() {
    if (true) {
      callbackUpDown(-1, 300);
      callbackTab();
      callbackLongPress();
    }
  }
};

class Menu {
  public:
    void UpDown(const int direction,
      const int count) {}
      void Tab() {}
};

class RTC {
  public:
    void Setup() {}
    void Toggle() {}
};

class Display {
  public:
    void Toggle() {}
};

Encoder encoder = Encoder();
RTC rtc = RTC();
Menu menu = Menu();
Display display = Display();

void setup() {
  rtc.Setup();
  encoder.Setup(menu.UpDown, menu.Tab, []{display.Toggle();rtc.Toggle();});
}

void loop() {
  encoder.Loop();
}

Вывод:

In file included from sun.ino:47:0:
callback.h: In function 'void setup()':
callback.h:52:75: error: invalid use of non-static member function 'void Menu::UpDown(int, int)'
   encoder.Setup(menu.UpDown, menu.Tab, []{display.Toggle(); rtc.Toggle();});
                                                                           ^
callback.h:28:7: note: declared here
  void UpDown(const int direction, const int count) {}
       ^~~~~~

Спасибо!

, 👍1

Обсуждение

Классы разработаны, чтобы быть самодостаточными. Если вам действительно нужно использовать функцию в другом классе, рассмотрите возможность рефакторинга кода, чтобы избежать этого. В качестве альтернативы вы можете поместить рассматриваемую функцию в новый класс, от которого наследуются оба существующих класса. Тем самым предоставляя доступ к этой функции из обоих существующих классов., @st2000

передать объект этого класса. https://arduinoprosto.ru/q/90329/invalid-use-of-non-static-member-function/90336#90336, @Juraj

Нет ли в C++ интерфейсов для внедрения зависимостей, инверсии зависимостей?, @Andre


2 ответа


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

1

Что вы здесь делаете

encoder.Setup(menu.UpDown, menu.Tab, []{display.Toggle();rtc.Toggle();});

не только передает указатель на функцию. Вы передаете указатель на функцию-член. Это на 2 уровня сложнее, чем указатели на функции сами по себе.

  1. Синтаксис объявления указателя на функцию-член отличается. Вам нужно включить имя класса в объявление указателя функции. Это будет выглядеть

    using CallBack2 = void( Menu::* )(const int, const int);
    
  2. При вызове указателя на функцию-член вам нужен объект для вызова функции.

К сожалению, это настолько сложно, что я не могу превратить ваш код в рабочий пример. Если вы хотите иметь возможность читать свой код через 3 месяца или если вы хотите, чтобы другие могли читать ваш код, придерживайтесь обычных указателей на функции. Они достаточно сложные.

Предложение: реализовать бесплатные функции, которые делают то, что вы хотите:

void menuUpDown(const int dir, const int count)
{
  menu.UpDown(dir, count);
}
void menuTab()
{
  menu.Tab();
}

void setup() {
  rtc.Setup();
  encoder.Setup(menuUpDown, menuTab, []{display.Toggle();rtc.Toggle();});
}

Или, поскольку вы уже используете лямбда-выражения (знаете ли вы об этом?), вы также можете

void setup() {
  rtc.Setup();
  encoder.Setup(
    [](const int dir, const int count){menu.UpDown(dir, count);}, 
    []{menu.Tab();},
    []{display.Toggle();rtc.Toggle();}
  );
}

Обратите внимание, что учащиеся и студенты могут быть незнакомы с концепцией лямбда-выражений. Если вы опубликуете такой код в Интернете, люди не смогут изменить его сами, потому что не понимают его.

,

Хороший ответ! Спасибо, @Andre

@Андре: всегда пожалуйста. После того, как вы попробуете предложение и убедитесь, что оно действительно работает для вас, вы можете пометить этот вопрос как решенный, поставив галочку под ответом, чтобы другие знали, что им больше не нужно тратить на это время. Не нужно торопиться, не торопитесь, чтобы убедиться, что это действительно тот ответ, который вам нужен., @Thomas Weller

@Andre: ваш дизайн больше похож на дизайн C # или Java. Будьте осторожны с этим на Arduino. У вас может не хватить памяти, если ваши проекты очень сложны, а ваш код слишком похож на компьютерный., @Thomas Weller

Что не так с моим дизайном и как я могу его улучшить? Я могу сделать классы статическими синглтонами и/или использовать указатели. Мне не нужно несколько экземпляров одного и того же класса. На самом деле мне нужен один экземпляр каждого класса. Как избавиться от нехватки памяти?, @Andre

@Andre: относительно OOM у меня мало советов. В основном избегайте «новых» (которые, как я вижу, вы не используете, это здорово). Далее, избегайте копий. Encoder encoder = Encoder(); может быть просто Encoder encoder;. (Да, в современном C++ мы гарантировали исключение копирования, но [никто не знает, какую версию C++ на самом деле поддерживает Android](https://arduinoprosto.ru/q/86242/what-c-standard-does-the-arduino -языковая поддержка)), @Thomas Weller

@Andre: для синглтонов не создавайте синглтоны. Может быть, кто-то действительно хочет создать другой объект. Я бы попытался сделать что-то подобное, как это было сделано для Serial. Вместо того, чтобы создавать свой собственный экземпляр Serial, есть только Serial, Serial1, Serial2 как предопределенные объекты (точно не знаю, как они это сделали), @Thomas Weller


0

Кроме того, использование статических методов или функций, не являющихся членами (стиль C)

class MyClass {
  public:
    static void onMsg(int num1, int num2) {
      // ПРИМЕЧАНИЕ. Здесь нельзя вызывать какие-либо нестатические методы!
    }
};

class LibraryClass {
  using CallBack = void( * )(int, int);
  private:
    CallBack callBack;
  public:
    void Setup(CallBack callBackFunc) {
      callBack = callBackFunc;
    }
    void Loop() {
      callBack(1, 2);
    }
};

MyClass myClass;
LibraryClass libraryClass;

void setup() {
  libraryClass.Setup(&myClass.onMsg); // Предоставляем экземпляр и функцию для вызова
}

void loop() {
  libraryClass.Loop();
}
,

Есть ли разница между &myClass.onMsg и MyClass::onMsg? Я не думаю, что это обеспечивает экземпляр для вызова функции. В противном случае вы можете вызывать функции-члены, и они не должны быть статическими. (Примечание: myClass со строчной буквой m не является классом, это объект; то же самое для libraryClass), @Thomas Weller