Помогите с ООП: Как передать объект в класс по ссылке?

Тема в разделе "Arduino & Shields", создана пользователем Patriot, 11 сен 2018.

  1. Patriot

    Patriot Нерд

    Всем привет! Знаю только базовый C, хочу чуть освоиться с C++ с ООП. И сразу прям затык с ходу :(

    В общем имеется простой скетч:
    Код (C++):

    #include <Adafruit_GFX.h>
    #include <SPI.h>
    #include <Wire.h>
    #include <Adafruit_ILI9341.h>
    #include <XPT2046_Touchscreen.h>
    #include "MainMenuScreen.h"

    #define TFT_DC 2
    #define TFT_CS 15
    Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
    XPT2046_Touchscreen ts(4, 5);

    MainMenuScreen screen(ts, tft);

    void setup(void) {
    tft.begin();
    ts.begin();
    tft.fillScreen(ILI9341_BLACK);
    screen.draw();
    }

    void loop()
    {
    }
     
    Файл MainMenuScreen.h:
    Код (C++):

    #ifndef MainMenuScreen_h
    #define MainMenuScreen_h
    #include <Adafruit_GFX.h>
    #include <Adafruit_ILI9341.h>
    #include <XPT2046_Touchscreen.h>

    class MainMenuScreen {

    public:
    MainMenuScreen(XPT2046_Touchscreen &ts, Adafruit_ILI9341 &tft);
    void drawScreen();
    protected:
    Adafruit_GFX_Button button;
    XPT2046_Touchscreen *ts;
    Adafruit_ILI9341 *tft;
    };

    #endif
     
    Файл MainMuneScreen.cpp:

    Код (C++):

    #include "MainMenuScreen.h"

    MainMenuScreen::MainMenuScreen(XPT2046_Touchscreen &ts, Adafruit_ILI9341 &tft)
    {
    this->ts = &ts;
    this->tft = &tft;
    }

    void MainMenuScreen::drawScreen()
    {
    tft->fillScreen(ILI9341_BLACK);
    button.initButton(
    &tft, 30, 120, 60, 240, ILI9341_DARKCYAN, ILI9341_BLUE, ILI9341_GREENYELLOW, "Кормление", 2);
    button.drawButton();
    }
     
    Что хочу сделать: в классе объявить (без инициализации) переменные соответствующего типа: для дисплея и для тачсенсора. Через конструктор передать объекты tft и ts по ссылке и положить в соответствующие переменные. Далее с ними работать внутри класса. Помогите правильно сделать на данном примере, а то опыт только с PHP, а там все по другому :(
     
  2. DetSimen

    DetSimen Спамовредитель Модератор

    Не надо ничего через конструктор передавать, инициализируй свои объекты при инициализации класса в котором они содержаться, зачем переменные плодить.
    только в обьявлении класса обьявляй не обьекты, а указатели на них, потом делай new и сё.
     
  3. DetSimen

    DetSimen Спамовредитель Модератор

    Код (C++):
    class TMyClass {
    private:
    TExternalClass1 *Class1;
    TExternalClass2 *Class2;
    public
    TMyClass(){
       Class1 = new TExternalClass1(...params...);
       Class2 = new TExternalClass2(...params...);
      }
    void Init(...params...) {
       Class1->init(...params...);
       Class2->init(...params...);
    }
    }
     
    Тогда ни одна скотина снаружи Class1 и Class2 не увидит и не испортит.
    А если нужно, чтоб увидела - тогда смысла в хранении этих обьектов внутри другого класса - нету, для того есть глобальные переменные (но это сильно неправильно, ревнители ООП за это бьют швабрами по коленям).
    Вапще, не зная архитектуры задачи, сложно штота советовать.
     
    Andrey12 нравится это.
  4. AlexU

    AlexU Гуру

    Объявление конструктора в классе:
    Код (C++):
    MainMenuScreen(XPT2046_Touchscreen * ts, Adafruit_ILI9341 * tft);
    т.е. принимает указатели.
    Реализация конструктора:
    Код (C++):
    MainMenuScreen::MainMenuScreen(XPT2046_Touchscreen * ts, Adafruit_ILI9341 * tft)
    {
    this->ts = ts;
    this->tft = tft;
    }
    запоминаем указатели в свойствах класса.
    Ну и соответственно создание объекта:
    Код (C++):
    MainMenuScreen screen(&ts, &tft);
    Такой подход можно применять, если Ваш класс 'MainMenuScreen' будет "уметь" работать с разными видами дисплеев и соотвтетственно с разными классами реализующими функционал работы с такими дисплеями. Тогда имеет смысл передавать указатели на объекты таких классов в конструктор. Но тогда конструктор в качестве параметров должен иметь указатели на базовые классы, которые будут наследовать те или иные реализации для конкретных дисплеев.
    Если же Ваш класс будет работать только с определённым типом дисплея, то городить огород с передачей ссылок или указателей не нужно. Можно в конструкторе сразу инициализировать свойства с нужными классы, а в конструктор в качестве параметров передовать данные необходимые для инициализации объектов нужных классов.
    При этом, если речь идёт об AVR-ках (платы типа Arduino UNO и т.п.), то про динамическое выделение памяти (операторы new, delete; функции malloc, free) лучше забыть -- меньше будет проблем.
     
  5. Mitrandir

    Mitrandir Гуру

    Не согласен, допустим у нас есть классы
    MainMenuScreen и SettingsScreen.

    Зачем каждому классу создавать свои экземпляры tft и тача? Они вполне могут разделять один экземпляр.

    да и бизнес-логике экрана совершенно не надо инициализировать библиотечные объекты экрана. Логичнее их получать из фабрики
     
    Patriot и DIYMan нравится это.
  6. Patriot

    Patriot Нерд

    Спасибо за примеры, попробую с ними. Но вот нашел сейчас еще такой вариант, точнее IDE Clion подсказала:
    Код (C++):

    MainMenuScreen::MainMenuScreen(XPT2046_Touchscreen &ts, Adafruit_ILI9341 &tft): ts(ts), tft(tft) { }
     
    Эта реализация чем-то отличается от вашего примера, или это одно и тоже но разный вид записи?
    Возможно я изначально не правильно задумал архитектуру, опишу задумку:

    В main.ino инициализируется дисплей и тачскрин. Далее есть переменная IBaseScreen *screen; (указатель?) на интерфейс IBaseScreen. От этого интерфейса будут создаваться последующие экраны, и класться через фабрику в эту переменную, а она в свою очередь выполнять screen->draw(); По логике, я не хочу каждый раз инициализировать дисплей в каждом классе, а хочу там только использовать уже готовый дисплей для вывода интерфейса и проводить обработку. Как-то так.

    О, пока писал, Mitrandir как раз описал задумку.

    PS: Вообще конечно очень классно развиваться в команде и с постоянным кодревью, жаль нет таких публичных сервисов для обмена опытом. Приходится одному вникать))
     
  7. DIYMan

    DIYMan Гуру

    А мужики-то не знали :) Нет там проблем.
     
  8. DIYMan

    DIYMan Гуру

    Тогда надо архитектуру создавать. Если ООП, то:

    1. Абстрактный класс, реализующий HAL для доступа к тому или иному экрану (общий набор функций, типа инициализации, отрисовки примитивов и т.п.);
    2. Фабрика, которая создаёт конкретный HAL;
    3. Абстрактный класс экрана, с методами типа update, draw и т.п.;
    4. Менеджер экранов, с методами типа addScreen, switchToScreen и т.п.;
    5. Конкретные реализации экранов;
    6. Диспетчер событий, который прозрачно от железа будет рассылать подписчикам события, типа - нажата кнопка на экране, или - нажата железная кнопка;
    7. Интерфейс подписки на события.

    Вот это, пмсм, уже будет вполне себе логика. Применяю подобный подход уже давно.
     
    Patriot, DetSimen и Mitrandir нравится это.
  9. AlexU

    AlexU Гуру

    Ни чем, "та же морковка - только вид сбоку". Только в своём примере я использовал указатели, а не ссылки. Наверно Вам в начале пути лучше оперировать указателями, пока не наступит полного понимания разницы между ссылками и указателями.
    Действительно это хороший повод создать объекты для дисплея и тачскрина и использовать указатели или ссылки на них. Но вот передавать как параметры может оказаться не самой лучшей идеей. Тут хорошо подойдёт шаблон проектирования -- синглтон -- http://cpp-reference.ru/patterns/creational-patterns/singleton/. По синглтону на дисплей и тачскрин.
     
    Mitrandir нравится это.
  10. AlexU

    AlexU Гуру

    Может мужикам стоит изучить проблему фрагментации памяти? Хотя, по правде говоря, она может возникнуть только при активном использовании связки new + delete (или malloc + free). Это первое, а второе при динамическом выделении прийдётся самому следить за расходом памяти, в отличие от статического, где система сборки подскажет о количестве потребляемой памяти без учёта кучи и стека. Всё же хоть какая-ни какая, но оценка потребления.
     
  11. DIYMan

    DIYMan Гуру

    Может, допустим хотя бы раз, что у мужиков активнейшим образом юзаются указанные механизмы 24х7 и при этом всё нормально с фрагментацией памяти? Так что, ещё раз - нет проблемы с new/delete malloc/free, есть проблема кривых рук.
     
    DetSimen нравится это.
  12. AlexU

    AlexU Гуру

    Это когда мужики знают что к чему... А когда человек задаёт вопросы, связанные с синтаксисом языка (до семантики ещё дело не дошло), то давать советы использовать динамическое выделение памяти такому человеку равносильно оказанию медвежьей услуги.
     
    DIYMan нравится это.
  13. DIYMan

    DIYMan Гуру

    Ничо, он в String сам сунется, и начнёт передавать его по значению :) Без знания основ, тащемта, нигде не обходится, и языки программирования - тут не исключение. Водить за ручку - такое себе занятие, человек задал вопрос - ему ответили. Хочет набивать шишки, наступая на грабли как следствие незнания основ - ну может, ему такой путь нравится, кто знает? А устраивать тут лекторий по основам языка - тоже, повторюсь, такое себе занятие.

    Думаешь (можно на "ты", да?) я не увидел, что там всё печалька, и до того, как браться за ООП - надо бы самые основы почитать? ;)
     
  14. AlexU

    AlexU Гуру

    Так класс String в контексте Arduino UNO (и подобных) вообще вселенское зло. Новичкам категорически использовать не рекомендуется -- char * -- "наше всё"....
     
    Mitrandir нравится это.
  15. DIYMan

    DIYMan Гуру

    И с char* огребаются :)
     
  16. Mitrandir

    Mitrandir Гуру

    ну тогда Char str[10];
     
  17. DIYMan

    DIYMan Гуру

    И обязательно запишут туда символ по индексу 10 :D
     
  18. Patriot

    Patriot Нерд

    Буду на это ориентироваться. Еще такой вопрос: экраны должны быть синглтонами? Можно ли в С++ реализовать синглтон на уровне базового абстрактного класса, что бы не переопределять конструкторы и инстансы в дочерних классах? Что-то вроде этого: ( return new self(); ) Или лучше сделать контроль за одним экземпляром класса в менеджере экранов? Фабрики и менеджер экрна точно должны быть синглтонами.
     
  19. DIYMan

    DIYMan Гуру

    По желанию. Например, у мну один класс обслуживает три экрана - они сходны во многом, и в данном конкретном случае оказалось, что лучше один класс, чем три разных ;) В основном - по экземпляру на экран, синглтонами не парюсь, чтобы не плодить статических переменных - просто выношу конструктор в private секцию и делаю публичный метод create - короче, полуфабрикат синглтона, на будущее :) А поскольку - проект свой, я уверен, что создаю по экрану за раз. Компромисс, короче.

    Заморочек много, но что-то похожее можно сделать: http://qaru.site/questions/508702/singleton-and-abstract-base-class-in-c

    А зачем, собственно, вообще усложнять? У меня сделано так: список (vector) экранов. У каждого экрана есть мнемоническое имя (или id, как угодно). У менеджера экранов есть методы типа addScreen - куда передаётся указатель на экземпляр экрана. Вот с этого метода и можно возвращать false, если экран не добавлен, например (пробежались по вектору, посмотрели, что экран с таким id уже есть, вышли; иначе - добавили экран). Считаю, тут главное - разумная достаточность: если пишете проект не для команды из N человек, где каждый может выстрелить себе в ногу - тогда можно скомпромиссничать и срезать часть архитектурного слоя, без особого ущерба для проекта в целом.

    Второе - да, но, опять же - я тупо обхожусь глобальный объектом менеджера, дав ему имя Screen. Насчёт первого - можно вообще не юзать, т.к. на лету менять железные дисплеи - такое себе, да и не особо нужно. В случае реализации конкретного HAL обхожусь директивами условной компиляции, типа

    Код (C++):
    #ifdef NOKIA_HARDWARE // экран от нокии
    ....
    #endif
    В общем, подходов разных много, но: мы говорили об ООП, а ООП - это не просто набор классов и используемых паттернов, поэтому я и развернул про интерфейсы, фабрики классов и пр. Без многого из этого можно обойтись, сохраняя стройность и прозрачность кода.
     
  20. DetSimen

    DetSimen Спамовредитель Модератор

    Как тебе на всю этухрень памяти хватает? О_О