Полиморфизм в ардуино?

TD27T
Offline
Зарегистрирован: 08.04.2016

Полиморфизма в ардуино, как я понимаю, в общем случае нет: нет STL, нет RTTI... Но хочется как-то все же иметь возможность вызывать функции производных классов по указателям на базовые.

Украл с просторов интернета (https://habrahabr.ru/post/182824/) такую вот штуку:

class RunnableInterface 
{
private:
    int  (*m_pfnRun_T)(void* const, int&); 
    template<typename T>
    static int Run_T(void* const pObj, int &k) { return static_cast<T*>(pObj)->siRun( k ); }
protected:
    template<typename T>
    void Init() { m_pfnRun_T = &(Run_T<T>); }
public:
    RunnableInterface(): m_pfnRun_T(0) {}
    int siRun(int &k) 
    {
      //  assert(m_pfnRun_T); // Make sure Init() was called.
        return (*m_pfnRun_T)(this, k); 
    }
    virtual ~RunnableInterface() {}
};
class Test: public RunnableInterface 
{
    friend class RunnableInterface;
private:
    int siRun(int &k) { k = m_value*2; return 0; }
protected:
    int m_value;
public:
    Test(int value): m_value(value) { RunnableInterface::Init<Test>(); }
};

Однако, эта конструкция может работать только с фиксированным типом указателя на функции, а хотелось бы параметризовать... Параметризовать m_pfnRun_T можно только путём указания шаблонным всего класса, но тогда теряется самый смысл - пробежать по списку указателей на базовые мы уже не сможем... Есть мысли, как такое можно организовать? Выручайте, друзья!

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

а, дуино тут при чём?

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

TD27T пишет:

Полиморфизма в ардуино, как я понимаю, в общем случае нет: 

Неправильно понимаете. В ардуино используется полный (от слова "совсем полный") язык С++. Всё, что в нём есть, всё есть и "в ардуино".

TD27T пишет:

нет STL, нет RTTI... 

Почему нет? В поставку не входит, но поставить и компилировать Вам никто не мешает.

TD27T пишет:

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

Кто-то или что-то мешает? Если Вы умеете это делать "не в ардуино", то сделаете и здесь. Похоже, мешает Вам то, что Вы не умеете этого делать самостоятельно (без готовых макросов), но это проблема не Ардуино, а Ваша.

TD27T пишет:

Есть мысли, как такое можно организовать?

Есть. Изучить язык (С++) и реализовать. Или поставить готовое решение откуда-нибудь и скомпилировать.

 

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Изучить язык (С++) и реализовать.

Чем и занимаюсь. Третий день, вот, ковыряю темплейты. Не самая простая тема, надо сказать. Полагаю, рассчитывать на помощь в этом деле было с моей стороны несколько наивно...

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

Если Вы умеете это делать "не в ардуино", то сделаете и здесь.

Не умею. Плюсы для меня не родной язык. Стараюсь постигать.

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

Похоже, мешает Вам то, что Вы не умеете этого делать самостоятельно (без готовых макросов), но это проблема не Ардуино, а Ваша.

Мне не нужны готовые макоросы, скетчи, и прочее. Мне нужна идея, принцип. Ну вот, как например, что можно установить stl, даже не думал об этом. Наверное, можно обойтись менее радикальными процедурами...

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

В ардуино используется полный (от слова "совсем полный") язык С++. Всё, что в нём есть, всё есть и "в ардуино"

Где-то здесь пробегала тема (не смог найти сходу), что в avr-gcc неполностью реализован стандарт, и, в частности, в части отсутствия stl. Как-то это у меня в голове отложилось и больше я к этому вопросу не возвращался.

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

TD27T пишет:

Полиморфизма в ардуино, как я понимаю, в общем случае нет

Не надо меня пугать. Вроде есть и работает.

class Cl_basic {
  public:
    virtual void setup() = 0;
    virtual void loop() = 0;
};
class Cl_led:  public Cl_basic {
    bool led;
    byte pin;
    uint32_t past;
  public:
    Cl_led(byte _pin): pin(_pin) {}
    virtual void setup() {
      pinMode(pin, OUTPUT);
      past = millis();
      digitalWrite(pin, led = 0);
    }
    virtual void loop() {
      if (millis() - past >= 300) {
        past = millis();
        digitalWrite(pin, led = !led);
      }
    }
};
//------------Компоновка-----------------
Cl_led *LED = new Cl_led(/*нога*/13);
Cl_basic *BASIC = (Cl_basic*)LED;

//-------------main()-------------------
void setup() {
  BASIC->setup();

}

void loop() {
  BASIC->loop();
}

 

TD27T
Offline
Зарегистрирован: 08.04.2016

Да, виноват, неверно выразился... У меня же шаблонные функции. Шаблонная функция не может быть виртуальной. Нужен именно механизм полиморфного вызова шаблонных функций.

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

TD27T пишет:

Да, виноват, неверно выразился... У меня же шаблонные функции. Шаблонная функция не может быть виртуальной. Нужен именно механизм полиморфного вызова шаблонных функций.

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

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
class Cl_basic {
  public:
    virtual void setup() = 0;
    virtual void loop() = 0;
};

template <class T> class Cl_led:  public Cl_basic {
    bool led;
    byte pin;
    uint32_t past;
  public:
    T obj;
    Cl_led(byte _pin, T _obj): pin(_pin), obj(_obj) {}
    virtual void setup() {
      pinMode(pin, OUTPUT);
      past = millis();
      digitalWrite(pin, led = 0);
    }
    virtual void loop() {
      if (millis() - past >= 300) {
        past = millis();
        digitalWrite(pin, led = !led);
      }
    }
};
//------------Компоновка-----------------
Cl_led<int> LED(/*нога*/13, 5);
Cl_basic *BASIC = (Cl_basic*)&LED;

//-------------main()-------------------
void setup() {
  BASIC->setup();

}

void loop() {
  BASIC->loop();
}

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

Вообще-то ООП эффективно и на Ардуино. Но другое дело, что люди использующие ООП , привыкли не экономить память. Это как мажора заставить жить как обычный человек, все равно спустит все в первый же день.

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

qwone, красиво - а смысл? _obj нигде не используется

TD27T
Offline
Зарегистрирован: 08.04.2016

qwone, это понятно. нужно что-то типа

class T0{
  public:
  template<typename...Args>
  void foo(Args...args){/*ничего не делаем*/;}
}
class T1:public T0{
  public:
  void foo(int& a){/*делаем раз*/;}
}t1;
class T2:public T0{
  public:
  void foo(int i, vloat b){/*делаем два*/;}
}t2;
void setup() {
  T0* t01=&t1;
  T0* t02=&t2;
  t01->foo(99);//делаем раз
  t01->foo(0, .3);//ничего не делаем
  t02->foo(99);//ничего не делаем
  t02->foo(0, .3);//делаем два
}

void loop() {}

 

TD27T
Offline
Зарегистрирован: 08.04.2016

b707 пишет:

вы что-то неправильно делаете, коли вам на Ардуино с ее количесвом памяти понадобились шаблонные функции.

Соглашусь. Просто привычка въевшаяся в мозг, ну вот придумал программу так, теперь мучаюсь с реализацией.

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

qwone пишет:

люди использующие ООП , привыкли не экономить память.

Не только память. Иногда результаты работы ООП оказываются, так сказать, зафиксированными в хардкоре. Например EMF файлы в винде. Программы разных производителей используют для создания EMF одну и ту же библиотеку GDI от MS. Но внутри файла сразу видно, где люди бездумно использовали ООП, а где думали головой. Размер файла с одной и той же картинкой может отличаться на порядок. Они даже на принтере печатаются с разной скоростью :)

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

 

В среде Ардуино  две эти строки надо писать одной строкой
template<typename...Args>
void foo(Args...args){/*ничего не делаем*/;}

template<typename...Args> void foo(Args...args){/*ничего не делаем*/;}

Вот така х..я малята.

 

TD27T
Offline
Зарегистрирован: 08.04.2016

qwone пишет:

 

В среде Ардуино  две эти строки надо писать одной строкой
template<typename...Args>
void foo(Args...args){/*ничего не делаем*/;}

template<typename...Args> void foo(Args...args){/*ничего не делаем*/;}

Вот така х..я малята.

 

Хм... У меня нормально компилится (ну не тот конкретно код, там надо очепятки поисправлять, но подобный). Но главную проблему это всё равно не решит: так или иначе, вызываться будет всегда T0::foo<Args...>(args...)

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

b707. Вот есть такая вещь. Мы все наблюдаем рост мощности камней. Но какова бы не была мощь камня, если не уметь написать программу, то смысла в этой мощности нет. А значит нет смысла увеличении мощности камня. Но камни то по мощности растут , растут и программы.  Так с переходом с Ассемблера на Си получился качественый скачек в вычислительной технике, как в железе, так и в программах. Понятно Си тоже не стоял на месте и расширялся, и это было еще до классов. Потом появились классы - новый скачек. И началась жопа. Человек остался все равно тупым. Даже классы стали для многих далеки, как до Москвы раком. Таким образом появились новые языки , понятные для недалеких пользоватей. Сейчас уже эти языки тоже выросли и набрались сложностей. 

  Это так преамбула. Даже Си++ уже исчерпало себя. Пришла эра Графических Интерфейсов. А без ООП создание их геморойная задача. Вот только ООП мало кто понимает. Я тоже . Да и что такое ООП именно в коде не описывается. 

   Так что если хотите что то путное сделать на Ардуине, нужно применять ООП. Даже если какжется что без него проще.

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

qwone, я почти со всем согласен. Но тут главное - вовремя остановится. Не применять ООП ради ООП. Не переборщить с абстракцией. Не ленится конкретизировать методы для производных классов, вместо использования обобщенных родительских.  Не описывать задачу слишком обще, когда этот код для конкретного применения. И, этого, кстати, стоит придерживаться, даже если ресурсы вроде бы не давят...

Примерчик из векторной графики, из опыта - так для развлечения.

Скажем, класс "буква", метод "напечатать букву"

1. выбрать канвас
2. инициализировать перо (толщину, цвет)
3. инициализировать фонт
4. переместить перо в точку х.у
5  нарисовать букву (!!!)
6. деинициализировать перо и фонт

... а теперь класс "строка", метод "напечатать строку" - думаю, уже все догадались...

For "каждая буква" do напечатать_букву...

А потом удивляемся, почему код такой медленный.

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

TD27T пишет:

qwone, это понятно. нужно что-то типа

class T0{
  public:
  template<typename...Args>
  void foo(Args...args){/*ничего не делаем*/;}
}
class T1:public T0{
  public:
  void foo(int& a){/*делаем раз*/;}
}t1;
class T2:public T0{
  public:
  void foo(int i, vloat b){/*делаем два*/;}
}t2;
void setup() {
  T0* t01=&t1;
  T0* t02=&t2;
  t01->foo(99);//делаем раз
  t01->foo(0, .3);//ничего не делаем
  t02->foo(99);//ничего не делаем
  t02->foo(0, .3);//делаем два
}

void loop() {}

 

Шаблоны я языке Си не могут быть виртуальными https://goo.gl/2dwsCj

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

b707 пишет:

qwone, я почти со всем согласен. Но тут главное - вовремя остановится. Не применять ООП ради ООП. Не переборщить с абстракцией. Не ленится конкретизировать методы для производных классов, вместо использования обобщенных родительских.  Не описывать задачу слишком обще, когда этот код для конкретного применения. И, этого, кстати, стоит придерживаться, даже если ресурсы вроде бы не давят...

Примерчик из векторной графики, из опыта - так для развлечения.

Вот та же векторная графика. Это и есть ООП. Реализовывать ее без ОПП жесть.  Еще одно дело рисовать ее в буфере ОЗУ, другое кидать на дисплей с узким каналом обмена. А мигание дисплея, как новички говорят из-за несихроного вывода на дисплей. А если тупо заносить в буфер канвас , а потом раз в секунду заливать на экран мигание пропадает. Вот только книг по ООП на ардуине не видел. Похоже тоже придется мне самому себе писать эту книгу .

TD27T
Offline
Зарегистрирован: 08.04.2016

qwone пишет:

Шаблоны я языке Си не могут быть виртуальными https://goo.gl/2dwsCj

Именно. Потому надо реализовать полиморфизм без использования механизма виртуализации. В первом посте топика, собственно, код, который именно это и делает. Но делает сугубо для функции с жестко заданной сигнатурой. А хорошо бы и для шаблонных.

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

qwone пишет:

Вот та же векторная графика. Это и есть ООП. Реализовывать ее без ОПП жесть. 

Так и есть. Я делал разбор вектора на языке без ООП - все равно фактически пришлось создавать обьекты в виде структур, из них связанные списки... (цепочки), потом связанные списки цепочек :)  - иначе жуть.

Но с другой стороны - пример выше с выводом строк побуквенно - я же не выдумал. В реальных файлах такое сплошь и рядом. Или, к примеру, ломаная линия, нарисованная с возвратом пера к нулевой точке после каждого сегмента. - явные издержки ООП

Забавно, что Микрософт даже запатентовал "метод оптимизации метафайлов" - заключающийся в удалении из кода тысяч ненужных инструкций повторной инициализации одинаковых обьектов :)

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

Инициализация, деницилизация это создание и уничтожение объекта в куче? Экономия памяти.  С другой стороны можно создать еще объект типа канвас , напечатать туда текст. То что вышло за рамки не печатать. Потом на общий канвас наложить новый канвас и удалить новый канвас. Так быстрее, но больше памяти используется.

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

TD27T пишет:

Третий день, вот,

Ну, Вы только в начале пути. Поговорим через полгода - не раньше.

TD27T пишет:

Полагаю, рассчитывать на помощь в этом деле было с моей стороны несколько наивно...

А какая тут может быть помощь? Знания в Вашу голову никто не положит. Работайте. Будут конкретные вопросы - задавайте.

TD27T пишет:

Мне нужна идея, принцип. 

Ну, попробуйте, может поможет: №1 и №2. В первом таи люди делают свои расширения для STL - вот можетепочитать как они это делают.

 

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Будут конкретные вопросы - задавайте.

Признаться, полагал, что вопрос задан достаточно конкретно. Попробую описать подробнее.

Итак, для каждого типа-наследника RunnableInterface генерируется собственная реализация Run_T, указатель на которую сохраняется в m_pfnRun_T. Тут всё просто и понятно. Если мы переходим к шаблонному описанию параметров siRun и, соответственно, Run_T, то одного указателя m_pfnRun_T нам уже явно недостаточно, т.к. для каждого отнаследованного класса будет уже несколько перегрузок Run_T, на все из которых нам нужны указатели. И эти указатели нам надо:

1. Получить.

2. Сохранить в какой-то структуре данных (какой?)

3. Исходя из параметров шаблона

template<typename R, typename...Args>
R siRun(Args...args)

реализовать выбор нужного указателя из контейнера (как?)
 

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

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

Кроме того, "как сделать" - неконкретный вопрос. Вот, мне нужна программа для управления буровой вышкой. Ответьте-ка мне "как сделать?"

И ещё, если Вы думаете. что когда я писал

ЕвгенийП пишет:
TD27T пишет:
Третий день, вот,
Ну, Вы только в начале пути. Поговорим через полгода - не раньше.

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

 

TD27T
Offline
Зарегистрирован: 08.04.2016

Там, выше http://arduino.ru/forum/programmirovanie/polimorfizm-v-arduino#comment-2... я давал пример кода, к которому стремлюсь. Видимо, он потонул во флуде и вы его не заметили.

Сейчас это реализуется вот таким вот примерно костылем:

enum mytypeid{
  typeT0,
  typeT1,
  typeT2
};
class T0{
  public:
  mytypeid t_id;
  T0():t_id(typeT0){}
  T0(mytypeid id):t_id(id){}
  template<typename...Args>
  void foo(Args...args){Serial.println("calling T0");}
};
class T1:public T0{
  public:
  T1():T0(typeT1){}
  void foo(int a){Serial.println("calling T1");}
}t1;
class T2:public T0{
  public:
  T2():T0(typeT2){}
  void foo(int i, float b){Serial.println("calling T2");}
}t2;
void setup() {
  Serial.begin(9600);
  T0* arr[] = {&t1, &t2};
  const int elems = sizeof(arr)/sizeof(*arr);
  for(int i = 0; i < elems; i++)
    if(arr[i]->t_id==typeT1)
      (static_cast<T1*>(arr[i]))->foo(99);
    else
      arr[i]->foo(99);
  for(int i = 0; i < elems; i++)
    if(arr[i]->t_id==typeT2)
      (static_cast<T2*>(arr[i]))->foo(0, .3);
    else
      arr[i]->foo(0, .3);
}

void loop() {}

Но хочется более элегантной реализации. Грубо говоря, инкапсулировать каст внутри самой вызываемой функции.

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

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

Почтайте более позднюю приписку в моём предудущем посте

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Вам нужно написать другой, исходя из задачи.

Не уверен, что правильно вас понял. Если вы о смене архитектуры конечного приложения, то, в данном случае, интерес у меня скорее академический, нежели практический: меня интересует принципиальная возможность подобной реализации и возможные подходы к решению задачи именно в такой постановке. Если же вы о том, что код из первого поста не подходит к решению проблемы из поста #9, то я понимаю, что он не подходит; его следует воспринимать, скорее как концепцию, которую можно (или нельзя?) развить в нужном направлении. Или же вы о том, что модифицировать чужой код в принципе методически неверно?

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

Почтайте более позднюю приписку в моём предудущем посте

Я понимаю, что вы не шутили.

 

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Даже если кто-то сейчас попытается объяснить Вам как делаются подобные вещи, Вы не поймёте.

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

TD27T
Offline
Зарегистрирован: 08.04.2016

В общем, почитал Уилсона и Александреску, покурил интернеты, побился головой о стену и пришел к выводу, что без rtti задача не решается. Ну, то есть, не решается иначе как через switch-case... Жаль.

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

TD27T пишет:

... покурил интернеты, побился головой о стену и пришел к выводу, что без rtti задача не решается. Ну, то есть, не решается иначе как через switch-case... Жаль.

Чкго Вы там покурили, что аж головой об стену биться стали? Вы, это ... не привыкайте к этой дряни.

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Чкго Вы там покурили, что аж головой об стену биться стали? Вы, это ... не привыкайте к этой дряни.

Что было. Головой о стену - это несколько тупиковых попыток получить информацию о типе объекта в рантайме. Но не вышел каменный цветок...

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

TD27T пишет:

 тупиковых попыток получить информацию о типе объекта в рантайме. 

Вцелом вывод верный. RTTI - чисто отладочное, оставлять его в релизе - ошибка. А использовать в МК - признак идиота не понимающего что творит. Но если Вам требуется инфа о типе переменной - скорей всего ошибка в архитектуре программы. Вероятно подход срисовали с какго перла богомерзкого, или простигосподи с пыхи.

ПС. К полиморфизму это разумеется не относится. 

TD27T
Offline
Зарегистрирован: 08.04.2016

Logik пишет:

если Вам требуется инфа о типе переменной - скорей всего ошибка в архитектуре программы

В общем-то, к такой постановке я пришел через реализацию пакета конечных автоматов интеракций с пользователем. Обобщая задачу можно сказать, что есть набор событий (нажатие кнопки, таймаут, etc) и есть набор объектов, которые на эти события могут реагировать. Соответственно, удобно было бы в коллбэках событий пробежаться по коллекции объектов общего базового класса и предложить каждому объекту отреагировать на событие. Но тут загвоздка в том, что каждое событие генерирует индивидуальный набор параметров: у кнопки их, к примеру, два; у таймера - один. Можно, конечно, прописать в базовом классе виртуальные функции типа checkButton(Button&, eventType), checkTimer(int) и перегружать их в соответствующих наследниках... Но это некрасиво. Понадобится нам, например, обрабатывать ввод пользователя в последовательный порт: мы наследуем, реализуем в наследнике нужный функционал и вынуждены в базовом классе сделать объявление соответствующей виртуальной функции (а если базовый класс объявляется как интерфейс - то и во всех остальных его потомках задать тело этой функции), от чего страдает парадигма повторного использования кода и мне становится грустно. Однако, базовому классу, если подумать, вообще не важно какие события могут обрабатывать его потомки, его дело - обеспечить полиморфный вызов нужной функции своего потомка. Ну и вот захотелось мне эдакий автоматически расширяющийся интерфейс, задающий лишь имя функции, но не количество и тип параметров.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

TD27T пишет:

Logik пишет:

если Вам требуется инфа о типе переменной - скорей всего ошибка в архитектуре программы

В общем-то, к такой постановке я пришел через реализацию пакета конечных автоматов интеракций с пользователем. Обобщая задачу можно сказать, что есть набор событий (нажатие кнопки, таймаут, etc) и есть набор объектов, которые на эти события могут реагировать. Соответственно, удобно было бы в коллбэках событий пробежаться по коллекции объектов общего базового класса и предложить каждому объекту отреагировать на событие. Но тут загвоздка в том, что каждое событие генерирует индивидуальный набор параметров: у кнопки их, к примеру, два; у таймера - один. Можно, конечно, прописать в базовом классе виртуальные функции типа checkButton(Button&, eventType), checkTimer(int) и перегружать их в соответствующих наследниках... Но это некрасиво. Понадобится нам, например, обрабатывать ввод пользователя в последовательный порт: мы наследуем, реализуем в наследнике нужный функционал и вынуждены в базовом классе сделать объявление соответствующей виртуальной функции (а если базовый класс объявляется как интерфейс - то и во всех остальных его потомках задать тело этой функции), от чего страдает парадигма повторного использования кода и мне становится грустно. Однако, базовому классу, если подумать, вообще не важно какие события могут обрабатывать его потомки, его дело - обеспечить полиморфный вызов нужной функции своего потомка. Ну и вот захотелось мне эдакий автоматически расширяющийся интерфейс, задающий лишь имя функции, но не количество и тип параметров.

Jon-Stewart-Eagerly-Watching-Eating-His-

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

TD27T пишет:

захотелось мне эдакий автоматически расширяющийся интерфейс, задающий лишь имя функции, но не количество и тип параметров.

Кто-то или что-то мешает сделать функцию с неопределённым набором параметров? Неужели ОН уже и это запретил?

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

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

Неужели ОН уже и это запретил?

он тихо жрёт попкорн и акуевает.

TD27T
Offline
Зарегистрирован: 08.04.2016

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

Кто-то или что-то мешает сделать функцию с неопределённым набором параметров? Неужели ОН уже и это запретил?

Мешает мне незнание того, как вызов этой функции передать по иерархии наследования.

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

TD27T пишет:

Обобщая задачу можно сказать, что есть набор событий (нажатие кнопки, таймаут, etc) 

Вот так бы сразу и писал - события! А значить нужны id событий. Его и передавайте в обработчик (калбеки или обекты или что там у вас будет), а лучше сохраняйте в очереди и потом обрабатывайте. А от id события будет зависеть неявно кол-во и тип параметров. И это верно, в отличии от попытки опознать событие по типу и числу параметров (может я чего у вас не понял, но похоже что так написали). И обработчики из иерархии сразу по id распознают свои события от чужих. Остается пустяк - где хранить сами параметры. Тут багатство выбора очень большое, как понравится вобщем 

TD27T
Offline
Зарегистрирован: 08.04.2016

Logik пишет:

 опознать событие по типу и числу параметров

Событие не опознается никак, это не нужно. Посмотрите код из поста #24: два цикла for там можно представить как обработчики двух разных событий. Просто выполняется приведение типа и вызов функции-члена.

Logik пишет:

 А от id события будет зависеть неявно кол-во и тип параметров

...

Остается пустяк - где хранить сами параметры.

Не такой уж и пустяк, на самом деле. Оно, конечно, можно упаковать параметры во что-нибудь типа "EventArgs e", унифицировать сигнатуру и приводить аргумент по месту... Но это решение несколько не то, чего я ищу. Хочется получить максимально очевидный и простой в использовании синтаксис. Пусть даже и ценой некоторых накладных расходов.

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

TD27T пишет:

Мешает мне незнание того, как вызов этой функции передать по иерархии наследования.

Так попробуйте, делов-то. нормально он передаётся.

TD27T
Offline
Зарегистрирован: 08.04.2016

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

TD27T пишет:

Мешает мне незнание того, как вызов этой функции передать по иерархии наследования.

Так попробуйте, делов-то. нормально он передаётся.

Хм... Я имел в виду, что функцию вида virtual void foo(some, ...) нельзя перегрузить функцией void foo(some, some1), придется тащить сигнатуру с переменным числом параметров в конечный класс, чего хотелось бы избежать для улучшения читаемости. Потому и шаблоны.

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

TD27T пишет:

Logik пишет:

 опознать событие по типу и числу параметров

Событие не опознается никак, это не нужно. Посмотрите код из поста #24: два цикла for там можно представить как обработчики двух разных событий. Просто выполняется приведение типа и вызов функции-члена.

так тот код вобще мало чем интересен, у вас if (arr[i]->t_id==typeT1)  и т.д. прям в цикле висит, что совсем не по ООПшному  - инкапсуляция нарушена, только обект должен знать свои обрабатываемые  id событий (кстати в общем случае не одно id, например нажатие и отпускание кнопки - два разных события, но интересны могут быть одному обработчику) А при больше двух событиях  такой подход, как в #24 дает нежеланный вами свич.

Должно быть типа 

for(int i = 0; i < elems; i++)

(arr[i]))->foo(mytypeid, Prm);
 
А уж внутри foo решится вопрос обрабатывать это  событие здесь или дальше крутит цикл. И о досрочном завершении цикла подумать. А отэтот Prm - это какраз параметры и от способа их хранения и передачи зависит что там будет. Может и ничего не быть, например при обмене через глобальные, может указатель который внутри foo приведется к тому что foo захочет для данного typeT1, может id какой в списке или стеке или еще чемто.. 
 

TD27T пишет:

Logik пишет:

 

Остается пустяк - где хранить сами параметры.

Не такой уж и пустяк, на самом деле. Оно, конечно, можно упаковать параметры во что-нибудь типа "EventArgs e", унифицировать сигнатуру и приводить аргумент по месту... Но это решение несколько не то, чего я ищу. Хочется получить максимально очевидный и простой в использовании синтаксис. Пусть даже и ценой некоторых накладных расходов.

Простой и элегантный - динамически распределяем структуру под параметры каждого события, передаем указатель на неё в foo как void* а внутри приводим как выше писал и освобождаем память после обработки события. Только не считайте что фрагментация кучи - только мелкие накладные расходы. Но вопрос таки пустяковый, т.к. много вариантов решения имеет. И все годные, хотя и с недостатками.