Передача параметров в библиотечный статический класс.

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

Функции для работы с неким LCD экраном собираются в библиотеку. Обычно в ней описан класс. В начале программы мы создаем его экземпляр, передавая параметры в конструкторе : LCD lcd(pin1,pin2,pin3). Дальше работаем с экземпляром, через его методы, например lcd.print("Hello World"); 



Особенность МК-based устройств в том, что в 99.99% случаев у них один экран, один GPS, одна клавиатура и т.д. А памяти очень мало. Поэтому я сделал все классы в таких библиотеках полностью статическими и 

не трачу память на экземпляры класса. Все параметры устройства хранятся в тоже статическом классе Settings, а не в членах lcd. В LCD определяются поля вида static const uint8_t dcPin = Settings::lcdDcPin и оперативная память не тратится. Такой периферии и параметров много и памяти экономится прилично. 



Всё хорошо пока не нужно использовать эту библиотеку(библиотеки) в нескольких проектах.  У них класс LCD у всех один, но он зависит от класса Settings, а он в каждом проекте свой. 



Интересно как прописать такую зависимость в библиотечный статический класс от локального класса не потратив ни байта оперативки? 

sadman41
Offline
Зарегистрирован: 19.10.2016

Пахнет тут наследованием и этим... как его, черт... полиморфизмом.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Очевидно, интерфейсом. Просто передавать указатель на конкретный класс Settings в библиотечный.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

sadman41 пишет:

Пахнет тут наследованием и этим... как его, черт... полиморфизмом.

Им самым ;)

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

задача не тратить ни байта оперативки, экземпляры LCD и Settings не создаются, в них все члены и методы статические, всё 100% static

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Соглашение по методам, другого варианта решения не завезут. Каждый конкретный Settings - имеет один и тот же набор методов, пусть они сто раз статическими будут.

Кстати сказать: можно просто namespace юзать, раз всё равно всё в статике. И проще будет, и писанины меньше.

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

Спасибо за ответ, расскажите пожалуйста по-подробнее что значит "Соглашение по методам"

p.s. namespace разумеется юзаю, и лямбды и scoped enumы и inline члены классов но это вообще из другой оперы и к этой теме отношения не имеет

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

kokuam пишет:
 Поэтому я сделал все классы в таких библиотеках полностью статическими и 

не трачу память на экземпляры класса. Все параметры устройства хранятся в тоже статическом классе Settings, а не в членах lcd. Интересно как прописать такую зависимость в библиотечный статический класс от локального класса не потратив ни байта оперативки? 

PROGMEM конечно нафиг. Он еще больше оперативки экономит.

Logik
Offline
Зарегистрирован: 05.08.2014

Еще нехватает номера пинов при каждом чихе из PROGMEM тянуть. Если уж совсем "не тратить ни байта оперативки" можете попробовать через extern организовать. Но думаю это уже излишняя экономия. Я в подобных ситуациях делаю базовый Setings от него наследую в каждый проект свой и передаю указатель на него в LCD. Причем номера пинов не даю. Даю методы обмена с экраном, типа отправить байт, блок данных, ресета и т.д.  

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

qwone пишет:

PROGMEM конечно нафиг. Он еще больше оперативки экономит.

Разумеется нафиг, PROGMEM это ближе к делу чем namespace, но если я в LCD описываю поле так
class Lcd {

...

 static const uint8_t dcPin = Settings::nokia_dc;

}

а в Settings {

   static const uint8_t nokia_dc = 7;

}

то в код будет подставляться именно константа, примерно как если-бы я пользовался define-ом, только с контролем типов и области видимости. Использовать PROGMEM тут не рационально т.к. придётся больше кода писать, больше флеша уйдёт на вызов pgm_read_xxx() и стека на вызов и на переменную в которую нужно читать из флеша.

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

Да, про extern тоже думал, но если я правильно понимаю возможности extern-а, extern-ом я могу объявить внешнюю переменную, но уже известного типа, например extern int x; а тип-то задаётся в локальном файле и мы вернулись к исходной проблеме.

У ДиХальта на форуме подсказали решение, в котором LCD - шаблонный класс, а Settings передается в него параметром, думаю, должно сработать.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

kokuam пишет:

Спасибо за ответ, расскажите пожалуйста по-подробнее что значит "Соглашение по методам"

Просто перечень методов, которые обязан реализовывать каждый конкретный Settings. Тогда LCD всегда будет знать, какие методы дёргать. Или я чего недопонял?

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017
В итоге получилось довольно изящно, может кому пригодится : 
это библиотека - xlcd.h - лежит в ...arduino/libraries/
 
#ifndef xlcd_h
#define xlcd_h
 
#include <Arduino.h>
 
template < class T >    
class Xlcd {
  static const uint8_t x = T::y;
 public:
  void static doSomething(){ Serial.println(x);}; 
};    
 
#endif 
 
 
а это выполняемый скетч:
 
#include <xlcd.h>
 
class Settings{
  public:
   static const uint8_t y = 25;
};
 
using lcd = Xlcd<Settings>;
 
void setup(){
  Serial.begin(9600);
  lcd::doSomething();
}
 
void loop() {}

 

Logik
Offline
Зарегистрирован: 05.08.2014

Ага, неплохо вроде.

Про extern - да, типы должны соответствовать , а в чем проблема то? Очевидно что параметро в Settings по типу должен соответствовать ожидаемому в LCD.

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

Проблема в том, что я описал в первом посте : LCD не может зависить от Settings т.к. в трех проектах Settings - у каждого свой. Разумеется, можно создать абстрактный ISettings и от него наследовать проектовые Settings и передавать их в конструкторе, но это мы вернулись к самому началу. LCD д.б. полностью статический класс.

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну как вариант это

/**/
typedef struct { /*создаем структуру набора настроек*/
  byte a;
  byte b;
  byte c;
} set_t ;
enum set_e {set1 = 0, set2, set3}; /*создаем имена набора настроек*/
const set_t setting[] {
  {1, 2, 3}/*набор set1*/
  , {1, 2, 3} /*набор set2*/
  , {1, 2, 3} /*набор set3*/
};
class Cl_AAA { /*некий класс*/
    byte a;
    byte b;
    byte c;
  public:
    void init(set_e i) {
      a = setting[i].a;
      b = setting[i].b;
      c = setting[i].c;
    }
};
//----------------------------------
Cl_AAA AAA;
//----------------------------------
void setup() {
  AAA.init(set1);
  //AAA.init(set2);
  //AAA.init(set3);
}

void loop() {

}
/**/

Но обычно используют компиляцию по условиям. Так меньше памяти занимает

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

Спасибо, но это прямая противоположность задаче, у вас в оперативке хранятся сразу все три набора настроек для всех трех проектов. Задача-же не тратить оперативку совсем и с помошью шаблонного класса это получается. В реальности приходится перекраивать библиотеки, но это дело житейское.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

const set_t setting[] прячте в PROGMEM. а в функции init() извлекайте. Все остальное не требует ОЗУ.

ПС:#248

А еще проще используйте условную компиляцию 

#define US 0 
#define ENGLAND 1
#define FRANCE 2

#define ACTIVE_COUNTRY US

#if ACTIVE_COUNTRY == US
  char currency[] = "dollar";
#elif ACTIVE_COUNTRY == ENGLAND
  char currency[] = "pound";
#else
  char currency[] = "franc";
#endif

от сюда http://cpp.com.ru/shildt_spr_po_c/10/1005.html

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017

пошли по кругу : я вам уже писал "Использовать PROGMEM тут не рационально т.к. придётся больше кода писать, больше флеша уйдёт на вызов pgm_read_xxx() и стека на вызов и на переменную в которую нужно читать из флеша."

макросы это суровое наследие C, в C++ лет 30 назад были придуманы шаблоны

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

kokuam пишет:

больше флеша уйдёт на вызов pgm_read_xxx() и стека на вызов

А вы это проверяли?

Если "да" - на сколько больше?

kokuam
kokuam аватар
Offline
Зарегистрирован: 10.01.2017
на 16 байт больше

-------------------------------------------

class Settings{

 public: 
  static const uint8_t dcPin = 7;
};
 
static const uint8_t dcPin PROGMEM = 7;
 
void setup() {
  
  pinMode( Settings::dcPin, OUTPUT );
  //pinMode( pgm_read_byte_near(&dcPin) , OUTPUT );
}
 
void loop() {}