Указатель на неопределенный класс ... ?

starcomputer
Offline
Зарегистрирован: 19.10.2021

Можно ли, и как, создать указатель на класс, если класс заранее неизвестен ?

Т.е. создать указатель на класс "вообще" ?

С заранее известными все понятно:

HardwareSerial *sp;
sp = &Serial;
sp->prinln("Это Serial");

А как быть если класс заранее неизвестен и может меняться программно (задаваться пользователем) ?

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

нельзя. Только просто указатель на "нечто" void

starcomputer
Offline
Зарегистрирован: 19.10.2021

Жаль. В  других языках это реализуемо.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Можно, конечно. В пределах разумного. Только Вы неверно формулируете задачу, потому не понимаете как её решить. Попробуйте сформулировать вопрос более чётко.

Указатель на "что-нибудь" объявляется очень просто : void *ptr; Но это жуткий говнокод и такой приём оправдан только в очень редких случаях. Чаще всего, если такое потребовалось, значит программа неверно спроектирована.

Иметь указатель на "класс вообще" вещь абсолютно бесполезная. Что Вы с ним будете делать? А вот указатель "на любой класс, у которого есть такой-то метод (такие-то методы)" - это без проблем, это общая стандартная практика. Делайте на здоровье.

starcomputer
Offline
Зарегистрирован: 19.10.2021

Задача:

Есть кнопка, по нажатию которой выполняется какое-то заранее неизвестное действие. Какое - задается пользователем в настройках (после компиляции). Есть несколько объектов разных классов, которые могут выполнять эти действия. Пользователь выбирает объект (по его имени) и функцию в объекте (тоже по имени). Имена заданы в самом классе. После настройки, при нажатии на кнопку должна выполниться заданная функция заданного объекта. Настройки могут изменяться во время работы программы.
 

starcomputer
Offline
Зарегистрирован: 19.10.2021

В смысле "на любой класс, у которого есть такой-то метод (такие-то методы)" ? Классы свои, я могу задать в них одинаковые (по имени) методы.

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Номер выбранного пользователем действия (сценария) в int, и дальше свитч-кейс на кнопке, не??

starcomputer
Offline
Зарегистрирован: 19.10.2021

В программе до десятка объектов + их функции. Количество возможных сценариев (не все практически имеют смысл) будет огромным. Но идея интересная, если не получится по-другому ....

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

starcomputer пишет:

В программе до десятка объектов + их функции. Количество возможных сценариев (не все практически имеют смысл) будет огромным. Но идея интересная, если не получится по-другому ....

Десяток объектов - фигня. Реализуйте каждый объект экземпляром класса, организуйте их в массив и будет вам щщастье. 

Либо, если каждый объект уникального класса, то таки свитч-кейс

SAB
Offline
Зарегистрирован: 27.12.2016

Автор, зачем такие заморочки? Если всё равно, после в настройках всё определяете. Ну определите какие то начальные условия переменных, а потом меняйте их сколько угодно. Согласен с Rumata. Хотя если это лабораторка, тогда препод попал в точку.

starcomputer
Offline
Зарегистрирован: 19.10.2021

Каждый объект и так реализован как экземпляр класса, но классы разные, в один массив их не загонишь. Количество объектов класса тоже заранее не известно. Известно только максимально возможное значение.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

В смысле "на любой класс, у которого есть такой-то метод (такие-то методы)" ? Классы свои, я могу задать в них одинаковые (по имени) методы.

Так какого рожна Вам ещё надо? Так и делайте. В чём проблема?

Или Вы не знаете как сделать указатель "на любой класс, у которого есть такой-то метод (такие-то методы)"?

 

starcomputer
Offline
Зарегистрирован: 19.10.2021

Это не лабораторка :) Последнюю "лабу" я делал лет 40 назад :)

Это контроллер. Куча устройств 1-wire, RS485, плюс Bluetooth, Wi-Fi, GSM. Пользователь определяет, что делать, если датчик показывает "такую-то" температуру. То-ли включить / выключить что-то, то ли отправить СМС.

starcomputer
Offline
Зарегистрирован: 19.10.2021

ЕвгенийП пишет:

Или Вы не знаете как сделать указатель "на любой класс, у которого есть такой-то метод (такие-то методы)"?

Не знаю. Подскажите.

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

starcomputer пишет:

Это не лабораторка :) Последнюю "лабу" я делал лет 40 назад :)

Это контроллер. Куча устройств 1-wire, RS485, плюс Bluetooth, Wi-Fi, GSM. Пользователь определяет, что делать, если датчик показывает "такую-то" температуру. То-ли включить / выключить что-то, то ли отправить СМС.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Не знаю. Подскажите.

Не ожидал. Вы так смело спорили о том, что такое ООП :-)

Вот пример, разбирайтесь

// Общий класс в котором просто перечислены все методы, 
// которые есть у всех конкретных классов 
// При этом whoAreYou будет общим для всех конкретных классов
// а method1 и method2 будут у каждого свои
struct CGlobal {
	virtual void method1(void) = 0;	
	virtual void method2(void) = 0;
	void whoAreYou(void) { method1(); method2(); }	
};

// Первый конкретный класс
struct KakaFirst : public CGlobal {
	void method1(void) { Serial.println("Это KakaFirst::method1"); }
	void method2(void) { Serial.println("Это KakaFirst::method2"); }
};

// Второй конкретный класс
struct KakaSecond : public CGlobal {
	void method1(void) { Serial.println("Это KakaSecond::method1"); }
	void method2(void) { Serial.println("Это KakaSecond::method2"); }
};

void setup(void) {
    Serial.begin(115200);

    // делаем указатели на "какой-нибудь класс"
    // и создаём объекты обоих конкретных классов
    CGlobal 
    	* ptr1 = new KakaFirst(),
    	* ptr2 = new KakaSecond();

    // Проверяем
    ptr1->whoAreYou();
    ptr2->whoAreYou();
}
void loop(void) {}
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Либо ты делаешь базовый класс и вызываешь методы дочерних по желанию, но тут не дельфи, rtti даже близко не пахнет, либо, что проще на контроллерах, заводишь указатели на функции и назначаешь их обработчиками кнопок (их можно переопределять по желанию), а внутри вызывай методы какого хочешь класса. Так можно сделать "плавающие" кнопки, повесить на любую кнопку любой отклик "на лету".  

starcomputer
Offline
Зарегистрирован: 19.10.2021

Другими словами вы создаете указатель на базовый класс, а затем присваиваете ему адрес объекта производного класса ?

class parent {
};

class child: public parent {
public:
child();
};

child::child(){}

child myChild();

parent *pp;
pp = myChild;

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

starcomputer пишет:

Другими словами вы создаете указатель на базовый класс, а затем присваиваете ему адрес объекта производного класса ?

Главное, чтобы  в базовом классе были определены ВСЕ функции которые могут быть вызваны в дочерних. Пусть даже они будут pure virtual.  

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ага. И созданный таким образом объект вполне законно является и объектом типа CGlobal, и объектом типа KakaFirst. Это и есть полиморфизм в данном случае.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Только помни, что по указателю на базовый класс нельзя вызвать функции дочерних, не описаных в базовом

starcomputer
Offline
Зарегистрирован: 19.10.2021

Да, создавая дочерний класс с такими-же функциями, Вы переопределяете функции родительского класса. Это я уже пробовал, но в базовом классе нужно создать много "пустых" процедур и функций, т.к. его наследники СЛИШКОМ разные :(

Я думал о чем-то типа:

class parent {};
class child: public parent{};
parent(child).function();

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

поздравляю. По ООП тебе твёрдая 2.  

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Да, создавая дочерний класс с такими-же функциями, Вы переопределяете функции родительского класса. Это я уже пробовал, но в базовом классе нужно создать много "пустых" процедур и функций, т.к. его наследники СЛИШКОМ разные :(

Вы мой пример видели? Что там по Вашему такое

	virtual void method1(void) = 0;	
	virtual void method2(void) = 0;

Если думаете, что что бы место заполнить, то таки нет.

Вспоминаю #23 (и продолжение)- это правда Вы были? Или Ваш экаунт взломали?

starcomputer
Offline
Зарегистрирован: 19.10.2021

С этим я пока не разобрался :(

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так Вы попробуйте переставить местами - сначала разбирайтесь, а потом посты пишите.

starcomputer
Offline
Зарегистрирован: 19.10.2021

Если бы я сам во всем разобрался, я бы не спрашивал :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А для чего я Вам пример писал? Уже начинаю жалеть потраченного времени :-(

negavoid2
negavoid2 аватар
Offline
Зарегистрирован: 06.05.2020

то, что нужно, называется интерфейс, в #15 вам показал Евгений Петрович, как надо. Может, в современном си ещё более удобные способы есть, уж не глядел я.

starcomputer
Offline
Зарегистрирован: 19.10.2021

ЕвгенийП пишет:

А для чего я Вам пример писал? Уже начинаю жалеть потраченного времени :-(

Сорри, отвлекают постоянно :(

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

static const char * operatios = "+-*/";

static const double first = 1234, second = 321;

static double add(void) { return first + second; }
static double sub(void) { return first - second; }
static double mul(void) { return first * second; }
static double div(void) { return first / second; }

typedef double (* OpFunc)(void);

static const OpFunc functions[] = { add, sub, mul, div };

void prompt(void) { Serial.println("Вводим операцию: + - * или /"); }

void setup(void) {
    Serial.begin(115200);
    Serial.print("Первое число: "); Serial.println(first);
    Serial.print("Второе число: "); Serial.println(second);
    prompt();
}

void loop(void) {
	if (! Serial.available()) return;
	const char * fRes = strchr(operatios, Serial.read());
	if (fRes) {
		Serial.println(functions[fRes - operatios]());
		prompt();
	}
}
starcomputer
Offline
Зарегистрирован: 19.10.2021

ЕвгенийП пишет:

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

Я бы хотел этого избежать (хотелок юзера), но пока не знаю как :( Контроллер не имеет заранее известного количества устройств, хотя я УВЕРЕН, что на практике их будет не более трех-пяти, причем у всех одинаковый состав - датчики температуры (2...4), датчики рН (0..2), исполнительный блок (1).

b707
Offline
Зарегистрирован: 26.05.2017

А с чего вы вообще вообразили, что можете создать такой контроллер? С вашими-то нулевыми знаниями?

starcomputer
Offline
Зарегистрирован: 19.10.2021

DetSimen пишет:

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

У меня реализован такой метод для кнопок (поскольку их заранее известное количество), но там однотипные указатели: typedef void (*pFunc)();

void btnSystem(){wnd[0].draw();}
void btnAqua(){wnd[1].draw();}

class BTN {
  public:
    BTN();
    uint16_t x1,y1,x2,y2;
    uint16_t width;
    uint8_t state; //0-Off,1-On,2-Auto,3-Touch
    String icon;
    String name;
    bool enable;
    void init(uint16_t bx, uint16_t by, uint16_t bw, String img, String nm, bool en,uint8_t bs, pFunc bf);
    void draw();
    pFunc pBtnTouch;
};

BTN::BTN(){
  btnCount++;
  }

void BTN::init(uint16_t bx, uint16_t by, uint16_t bw, String img, String nm, bool en, uint8_t bs, pFunc bf){
  x1 = bx;
  y1 = by;
  x2 = x1+bw;
  y2 = y1+BTN_HEIGHT;
  width = bw;
  icon = img;
  name = nm;
  enable = en;
  state = bs;
  pBtnTouch = bf;
}

void BTN::draw(){
  uint8_t x=x1+4;
  lcd.setColor(BTN_COLOR);
  lcd.setBackColor(BTN_COLOR);
  lcd.setFont(Icon);
  lcd.fillRoundRect(x1,y1,x2,y2);
  lcd.setColor(BTN_OUTLINE_COLOR);
  lcd.drawRoundRect(x1,y1,x2,y2);
  if(enable) lcd.setColor(BTN_FONT_COLOR);
  else lcd.setColor(GRAY);
  if(state==3){
    if(icon != ""){
      lcd.setFont(Icon);
      lcd.print(icon,x,y1+4);
      x+=42;
      }
    if(name != ""){
      lcd.setFont(BTN_FONT);
      lcd.print(name,x,y1+(BTN_HEIGHT-lcd.getFontYsize())/2);
      }
    }
  else lcd.print(btnState[state],x,y1+(BTN_HEIGHT-lcd.getFontYsize())/2);
}

BTN btn[BTN_COUNT]; 

При нажатии на кнопку (тачскрин) "открывается" определенное окно (или другие действия, для кнопок ручного управления).

starcomputer
Offline
Зарегистрирован: 19.10.2021

b707 пишет:
А с чего вы вообще вообразили, что можете создать такой контроллер? С вашими-то нулевыми знаниями?

Ну это не первый контроллер :) Но там программу писал не я.

https://reefcentral.ru/articles/151/6759/

Ну и такое еще:

https://reefcentral.ru/articles/151/7189/

 

rkit
Offline
Зарегистрирован: 23.11.2016

Прочитай любое базовое введение в классы и зачем они нужны. Там всё есть.

starcomputer
Offline
Зарегистрирован: 19.10.2021

ЕвгенийП пишет:

Вот пример, разбирайтесь

// Общий класс в котором просто перечислены все методы, 
// которые есть у всех конкретных классов 
// При этом whoAreYou будет общим для всех конкретных классов
// а method1 и method2 будут у каждого свои
struct CGlobal {
	virtual void method1(void) = 0;	
	virtual void method2(void) = 0;
	void whoAreYou(void) { method1(); method2(); }	
};

// Первый конкретный класс
struct KakaFirst : public CGlobal {
	void method1(void) { Serial.println("Это KakaFirst::method1"); }
	void method2(void) { Serial.println("Это KakaFirst::method2"); }
};

// Второй конкретный класс
struct KakaSecond : public CGlobal {
	void method1(void) { Serial.println("Это KakaSecond::method1"); }
	void method2(void) { Serial.println("Это KakaSecond::method2"); }
};

void setup(void) {
    Serial.begin(115200);

    // делаем указатели на "какой-нибудь класс"
    // и создаём объекты обоих конкретных классов
    CGlobal 
    	* ptr1 = new KakaFirst(),
    	* ptr2 = new KakaSecond();

    // Проверяем
    ptr1->whoAreYou();
    ptr2->whoAreYou();
}
void loop(void) {}

Вот эту строку можете объяснить ?

void whoAreYou(void) { method1(); method2(); } 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Вот эту строку можете объяснить ?

void whoAreYou(void) { method1(); method2(); } 

Самая обыкновенная функция.

Может, так понятнее?

void whoAreYou(void) {
   method1();
   method2();
} 

 

starcomputer
Offline
Зарегистрирован: 19.10.2021

Хм ... это я перегрелся :) Я же сам так часто пишу, не построчно :) Сорри.
Остальное вроде понятно. 
Хочу еще попробовать создать ОДИН класс для всего (собственно "все" это 5 классов), который состоит из указателей на другие классы. Что-то такое:
 



OneWire *ow;
class ALL {
Bluetooth *bt;
GSM *gsm;
WiFi *wf;
ow ow1[20];
}

Тогда по идее к любому устройству можно будет обращаться через ALL.bt.read(); или там ALL.ow1[12].write();

starcomputer
Offline
Зарегистрирован: 19.10.2021

Лучше не класс, а структуру :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Лучше не класс, а структуру :)

1. В чём, по-Вашему, разница между классом и структурой?

2. Что было у меня в примере?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:



OneWire *ow;
class ALL {
Bluetooth *bt;
GSM *gsm;
WiFi *wf;
ow ow1[20];
}

Тогда по идее к любому устройству можно будет обращаться через ALL.bt.read(); или там ALL.ow1[12].write();

Насчёт "по идее" не знаю, может и можно, а на практике - так обращаться нельзя :-(

starcomputer
Offline
Зарегистрирован: 19.10.2021

Почему ? Такой метод работает хорошо, ну по крайней мере у меня работает хорошо, в ЧАСТНОМ проекте.

Если у меня есть класс, в котором много объектов, которые обращаются к функциям одного и того-же класса, то зачем мне все функции этого класса дублировать ? В своем классе я просто указываю ссылку на класс с процедурами / функциями.
 

class cDS18B20 {
  public:
    cDS18B20();
    uint8_t rom[8];
    uint8_t data[13];
    uint8_t group=0;
    uint8_t num=0;
    float therm=0;
  private:
    OneWire *ow;
};
cDS18B20::cDS18B20(){
  ow = &OW;
  }

Объектов класса cDS18B20 много, но они все "висят" в одной сети - OneWire и используют функции / процедуры класса сети.

Это плохо ? :(

starcomputer
Offline
Зарегистрирован: 19.10.2021

Да,

OneWire OW(OW_PIN);

Там еще один класс будет добавлен, сеть другого типа.

 

starcomputer
Offline
Зарегистрирован: 19.10.2021

Немного не по теме ...
 

class XXX {
public:
XXX();
};

XXX::XXX(){
pinMode(ppp, OUTPUT);
digitalWrite(ppp, HIGH);
}

процедуры pinMode и digitalWrite не работают, т.е. ВООБЩЕ ничего не происходит. Пин так и остается INPUT.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Почему ? Такой метод работает хорошо, ну по крайней мере у меня работает хорошо, в ЧАСТНОМ проекте.

Т.е. Вы хотите сказать, что при описании

class ALL {
Bluetooth *bt;
GSM *gsm;
WiFi *wf;
ow ow1[20];
}

Вы в своём частном проекте используете конструкцию

ALL.bt.read(); 

и оно

starcomputer пишет:

работает хорошо

???

Боюсь, что Вы единственный, у кого это хорошо работает. У всех остальных такое просто не будет компилироваться :-(

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Немного не по теме ...
 

class XXX {
public:
XXX();
};

XXX::XXX(){
pinMode(ppp, OUTPUT);
digitalWrite(ppp, HIGH);
}

процедуры pinMode и digitalWrite не работают, т.е. ВООБЩЕ ничего не происходит. Пин так и остается INPUT.

Привыкайте выкладывать скетч полностью. Я же Вам полностью выкладывал - бери и запускай. Так чего ж Вы мне огрызки суёте?

В этом кусочке я, например, вообще не вижу с чего бы ему работать. А додумывать за Вас что там ещё написано ... мало ли что я додумаю.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

У тя область видимости класса XXX скорее всего, глобальная

starcomputer
Offline
Зарегистрирован: 19.10.2021

Весь класс целиком. Рабочий.

String charParity[3] = {"N","O","E"};

class HC05 {
  public:
  HC05(HardwareSerial &portBT);
  bool init();
  String name;
  String address;
  uint16_t baud;
  uint8_t stopBit;;
  uint8_t parity;
  uint8_t mode = UART;
  bool debug;
  void clr();
  bool empty();
  void setMode(uint8_t md);
  String readStr();
  String at(String cmd);
  void parse(String s);
  uint32_t color;
  private:
    HardwareSerial *port;
};

HC05::HC05(HardwareSerial &portBT){
    port = &portBT;
    debug = false;
}

bool HC05::init(){
  pinMode(BT_PRG, OUTPUT);
  digitalWrite(BT_PRG, LOW);
  pinMode(BT_RST, OUTPUT);
  digitalWrite(BT_RST,LOW);
  delay(100);
  digitalWrite(BT_RST, HIGH);
  delay(100);
  port->begin(38400);
  delay(1000);
  if(at("AT").indexOf("OK") != -1){
    color = LIME;
    return true;
    }
  else {
    color = GRAY;
    return false;
    }
}

void HC05::clr(){
  while(port->available() > 0) port->read();
}

bool HC05::empty(){
  if(port->available()) return false; else return true;
}


void HC05::setMode(uint8_t md){
  if(md == AT) {
    digitalWrite(BT_PRG, HIGH);
    mode = AT;
    }
  if(md == UART) {
    at("AT+RESET");
    digitalWrite(BT_PRG, LOW);
    mode = UART;
    }
  delay(500);
}

String HC05::readStr(){
  long timing;
  String s="";
  char c;
  timing = millis();
  while(millis()-timing < TIME_OUT)
    while(port->available() > 0){c = port->read(); s+=c;}
  if(debug) Serial.print("BT->"+s);
  return s;
}

String HC05::at(String cmd){
  if(mode != AT) setMode(AT);
  clr();
  port->println(cmd);
  delay(100);
  return readStr();
}

void HC05::parse(String s){
  uint8_t i,l,pNum;
  String pName;
  String param[5];
  l = s.length();
  i=0;
  pNum=0;
  param[pNum]="";
  pName="";
  // имя команды
  while((s[i] != ':') and (s[i] != '=')){
    if(s[i] != '+') pName = pName+=s[i];
    i++;
    }
    i++;
  // параметры
  while((s[i] != 10) and (s[i] != 13)){
    if(s[i] == ',') {pNum++; param[pNum]="";}
      else param[pNum] = param[pNum]+=s[i];
      i++;}
  // запись данныхж    
  if(pName == "NAME") name = param[0];
  if(pName == "ADDR") address = param[0];
  if(pName == "UART") {baud = param[0].toInt(); stopBit = param[1].toInt(); parity = param[2].toInt();}
}

 

 

starcomputer
Offline
Зарегистрирован: 19.10.2021

Вы правы, ТАК не работает :(

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

starcomputer пишет:

Весь класс целиком. Рабочий.

Я уже перестал понимать к чему это. К #42 или к #44?

Но, в любом случае повторю второй раз (третий раз повторять не буду, просто буду игнорировать Ваши вопросы). Если Вы хотите задать вопрос, приводите скетч полностью, чтобы его можно было просто взять и запустить без дописывания и/или удаления чего-либо. Если будете приводить огрызки, ответов от меня больше не будет никаких.