Полиморфизм или шаблоны?

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

Вопрос из области интересов qwone, но может и кто другой подскажет?

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

Соответсвенно, все это может быть оформлено через полиморфизм или через шаблоны. Для примера два коротких кода:

Полиморфизм:

#include <stdio.h>
class MyPar {
 public:
     int x;
     
     virtual void set_par() {};
   
};

class MyParInt: public MyPar {
   public:
      int pp1;
      
      void set_par(int a, int c) {
          x=a; pp1 =c;}
};

class MyParFloat: public MyPar {
   public:
      float pp1;
      
      void set_par(int a, float c) {
          x=a; pp1 =c;}
};
int main() {

MyPar* aa[2];
MyParInt pi;
MyParFloat pf;

int i,y,z,g;
y =3;
z =5;
g =55;
float f = 333.44;
pi.set_par(y,z);
pf.set_par(g,f);

aa[0] = &pi;
aa[1] = &pf;
for (i=0; i<2; i++) {
   MyParInt* my_int = dynamic_cast<MyParInt*> (aa[i]);
   if (my_int) printf("class.x class.pp1 %d %d \n", my_int->x, my_int->pp1);
   else {
     MyParFloat* my_int = dynamic_cast<MyParFloat*> (aa[i]);
     if (my_int) printf("class.x class.pp1 %d %.2f \n", my_int->x, my_int->pp1);
    }
}
}

 

Шаблоны

#include <stdio.h>
struct AbstractPar{
 virtual void set_par() {}; 
};

template <class T>
class MyPar:public AbstractPar {
 public:
     T pp1;
     int x;
    
     void setpar(int a, T b) {
         x=a; pp1=b;};
};


int main() {

AbstractPar* aa[2];
MyPar<int> pi;
MyPar<float> pf;

int i,y,z,g;
y =3;
z =5;
g =55;
float f = 333.44;
pi.setpar(y,z);
pf.setpar(g,f);

aa[0] = &pi;
aa[1] = &pf;
for (i=0; i<2; i++) {
  MyPar<int>* my_int = dynamic_cast<MyPar<int>*> (aa[i]);
  if (my_int) printf("class.x class.pp1 %d %d \n", my_int->x, my_int->pp1);
  else {
    MyPar<float>* my_int = dynamic_cast<MyPar<float>*> (aa[i]);
    if (my_int) printf("class.x class.pp1 %d %.2f \n", my_int->x, my_int->pp1);
    }
} 
}

Как видно, функция main(). практически одинакова, а вот описание классов в шаблонной версии чуть короче.

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

И второй вопрос - приведенный обьект my_int (напр строка 42 первого кода) - образуется копированием исходного обьекта? Нужно ли обязательно определять в исходных классах деструкторы, чтобы правильно подчищать my_int после динамического приведения?

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

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

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

ЕвгенийП,

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

Для удобства (и из программисткой лени :) решил описать команды как обьекты. Каждый обьект содержит имя команды. краткое описание (3-5 слов), ссылку на изменяемый параметр или колбек исполняемой функции.

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

 

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

Ну, я бы просто перегрузил бы конструктор и методы.

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

ЕвгенийП,

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

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

Нет.

Полиморфизм, это возможость обрабатывать объекты производных классов, в качестве объетков базовых классов.

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

А перегрузка операций - гораздо более простая вещь. Она и с наследованием-то никак не связана и может применяться не только в классах, а в любом другом пространстве имён (хоть на глобальном уровне). Это означает лишь то, что мы можем в одном и том же пространстве имён задать несколько функций и одинаковым именем, но различающихся по кколичеству и/или типам параметров. Компилятор сам разберётся какую из них вызывать, в зависимости от того, какие параметры передаются.

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

int func(int a)

и

int func(double a)

просто разные функции и внутренние (создаваемые компилятором) имена у них будут разными.

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

b707 пишет:

Вопрос из области интересов qwone, но может и кто другой подскажет?

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

Соответсвенно, все это может быть оформлено через полиморфизм или через шаблоны. Для примера два коротких кода

А Вы уверены что это два разных примера? ;)

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

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

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

Полиморфизм, это возможость обрабатывать объекты производных классов, в качестве объетков базовых классов. 

Офигеть как удивлен! Вы ж вроде препод -"язычник", а такую хрень пишите. Полиморфизм никак не привязан к классам и обектам. Вики в студию ! https://ru.wikipedia.org/wiki/Полиморфизм_(информатика)

В языках программирования и теории типовполиморфизмом называется способность функцииобрабатывать данные разных типов[1][2][3].

Существует несколько разновидностей полиморфизма. Две наиболее различных из них были описаны Кристофером Стрэчи[en] в 1967 году:

Тогда вобще еще до классов еще было жить и жить.

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

Logik пишет:

А Вы уверены что это два разных примера? ;)

Логик, спасибо, я и сам об этом подумал, когда у меня функция main() в обоих вариантах получилась практически одинаковая :)

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

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

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

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

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

Википедик? Ну-ну.

Кстати, кто Вам сказал, что я предод? Последний раз я преподавал более 10 лет назад.

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

Не предод всеже )))

Преподавание калечит навсегда, и видно издалека:( 

А пример полиморфизма без классов и прочего новодела -пожалуста, printf(). Оч неплохо справляется с разными типами. Но это не по теме.

По теме - а какие еще подходы можно применить по задаче ТС? Вполне можна указатели на функции обработки разных данных замутить. И насовать в массив. Там полная свобода. Вызывай какую хош с любыми параметрами. А завалится - сам виноват. Или передавать эти указатели параметром в функцию общей обработки данных, а внутри уже вызывать для действий специфических каждому типу. Или писат функции получающую void* и id типа и умеющую обрабатывать разные типы различая по id. Вобщем пути есть, есть где поизвращатся, было бы желание. 

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

Logik пишет:

По теме - а какие еще подходы можно применить по задаче ТС? Вполне можна указатели на функции обработки разных данных замутить. И насовать в массив. Там полная свобода. Вызывай какую хош с любыми параметрами. А завалится - сам виноват. Или передавать эти указатели параметром в функцию общей обработки данных, а внутри уже вызывать для действий специфических каждому типу. Или писат функции получающую void* и id типа и умеющую обрабатывать разные типы различая по id. Вобщем пути есть, есть где поизвращатся, было бы желание. 

Ну да, вариантов много. Для начала я сделал так - завел в классе указатели на переменные обоих типов - и инт и флоат. В зависимости от типа параметра нужный указатель присваивал, а другой оставался NULL. И простенький метод класса is_float(), сущность которого понятна из названия.

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

Но при большом разнообразии данных и методов обработки этот путь очень затратный.

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

b707 пишет:
Но при большом разнообразии данных и методов обработки этот путь очень затратный.
Здесь вопрос не в затратности, а в скажем так "в неразработаности нужных мускул". Почему новички топчатся на одних и тех же граблях. Да потому что они не знают много. А что бы узнать и применять надо это тренировать. Так что затраты идут только на тренировку. А после "развитых мускул программирования" все идет легко. Тем более Си++ позволяет создавать более объемные проекты.

ПС: Вот только научится на Си++ ООП сложновато. Капитализм блин не расчитан на проработку знаний. Так что программирование все больше скатывается в мистицизм.

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

Точно википедик.

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

он пианист и пидагог... все темы вопрошающих рано или поздно скатываются в грёбаный треш.

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

прежде чем вы все скатитесь в треш с преференсансом и медичками - ответьте на вопрос из последнего абзаца заголовка темы

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

b707 пишет:

прежде чем вы все скатитесь в треш с преференсансом и медичками - ответьте на вопрос из последнего абзаца заголовка темы

да! отвечайте все, пока не началось...

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

UPDATE

Тема оказалась теоретической.

Как выяснилось, ни один из кодов, приведенных в хаголовке - в Ардуино IDE не компилируется ( я обычно сначала упражняюсь в обычном текстовом редакторе и компилирую gcc /g++ под линуксом, а в ардуино переношу потом - уж больно IDE медленная).

Этот код под g++ собирается и работает, а в Ардуино IDE выдает ошибку:

error: 'dynamic_cast' not permitted with -fno-rtti

С опцией компилятора -frtti компиляция проходит, но не идет линковка. Вычитал на arduino.cc, что якобы avr-gcc не поддерживает RTTI.

В общем, придется остаться на первоначальном коде, который я описывал в сообщении #11. Этот код собирается без проблем и уже работает в реальном девайсе.

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

b707 пишет:

прежде чем вы все скатитесь в треш с преференсансом и медичками - ответьте на вопрос из последнего абзаца заголовка темы

Я просто боюсь отвечать. В тему набежали википедики (это люди, которые увидев молознакомое слово, лезут в википедию и, нахватавшись там высоких знаний,  начинают с мегаапломбом поучать специалистов, такое уже было с Лиспом и с оптимизацией в C++). В холиварах я с некоторых пор не участвую (освоил метод Овечкина), а потому предпочитаю уйти из темы. Но, прежде, чем уйти, постараюсь ответить на Ваш вопрос.

Так вот, по поводу вопроса в последнем абзаце, я его просто не понял. В строке 42 на самом деле никакого копирования исходного объекта не происходит. В этом легко убедиться, если поставить отладочную печать в конструкторе. Потому никаких танцев с бубнами не нужно. Другое дело, если бы Вы явно определили операцию преобразования типа. Но тогда всё было бы в Ваших руках.

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

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

Евгений, спасибо!

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

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

 Ещё сильнее уёл бы тупого "препода".

Так и так нормально получилось ;)

Если есть претензии к общепринятому определению полиморфизма приведеному в вики - можете его попробовать его исправить. Только сомниваюсь в успехе ;)

А приводить ссылки на определения полиморфизма в C# не надо, потому как к ардуине и С/С++ это никак не относится. Реализация же полиморфизма в С# действительно возможна только в рамках классов, по простой причине: любая функция в С# может быть объявлена только в рамках класса. Полиморфной функции вне класса быть в C# не может, потому как никакой функции вне класса в С# нет. А в Си/С++ - есть. И могут реализовывать полиморфизм.

Признавайтесь, ссылаясь на определение из  С#, Вы сознательно пытались манипулировать или просто не знали и C# тоже?

ПС. Вобще полиморфизм в програмировании (про биологию вам пишет ЕвгенийП) встречается часто и отличается многообразием, например в Си верно 1+1 и 1.0+1.0 Очевидно оператор + полиморфен.  А в некоторых языках и "ab"+"cd" тоже верно. А в некоторых и для переменных во время исполнения сработает. Полиморфизм - он такой. Вездесущий, гибкий и никак не завязан с ООП. Наоборот ООП на нем основано на 1/3.

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

...ну, вот - началось.

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

О_О

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

Нет, Logic, холивара не будет. Вы сказали глупость, и Бог с Вами, не впервой. чай. Ничего Вам доказывать я не намерен и не обязан. Давайте попрощаемся.