Мастер по C++. А вот скажи мне, как бы так в C++ обойти отсутствие generic'ов? Вот есть у меня N классов. Все они производные от одного и того же класса. Даже скажу подробнее, чтобы было понятнее. Это геометрические примитивы. Типа точка, отрезок, прямая, окружность, и чёртегознает ещё мне в голову придёт добавить в этот список потом.
И хочу я расстояние между ними вычислять. В C++ есть перегрузка функций -- это почти то что мне надо, но выбор реализации функции в ней выбирается статически, что полностью обесценивает перегрузку и делает её совершенно бесполезной.
Как-то незаметно я привык к generic'ам из common lisp'а, к тому что это просто, и когда здесь писал перегруженные функции distance, подумал, что ежели потребуется в рантайме выбирать нужную функцию, то я как-нибудь выкручусь, ведь в C++ есть rtti. Ан не тут-то было.
Я поискал в гугле насчёт generic'ов и C++, но там вообще вынос мозга. Пишут что в C# есть generic'и, но там синтаксис как у темплитов, и это такой вынос мозга... Нашёл какую-то статейку, где вроде про дженерики в C++, но там хрен поймёшь: в C++ или в C#. Тоже синтаксис я тя пну и source кода не видно. Дальше я не вникал.
Более конкретно, вот есть у меня две переменные:
И хочется как-то избавится от необходимости в таком коде:
Code: Select all
if(typeid(*p1) == typeid(point) && typeid(*p2) == typeid(point)) {
return point_point_distance(p1, p2);
} else if(typeid(*p1) == typeid(point) && typeid(*p2) == typeid(line_segment)) {
return point_segment_distance(p1, p2);
} else if...
Понятно тут можно оптимизировать и уйти от N*N if'ов, сделать N+N, а учитывая коммутативность ещё поделить количество if'ов примерно на 2. Но дело в том, что... по-моему всё равно уродство. Да и добавляя новый класс придётся лезть в эти if'ы, дописывать чего-то, причём так, чтобы не пропустить ни одного места, где можно пропустить. Можно конечно кодогенератор написать, но опять же, попахивает идиотизмом: специальный кодогенератор ради какой-то мелочи. Да и как этот кодогенератор можно научить выудить из заголовка список классов производных от primitive? Утонешь в регекспах.
Приходит в голову другой вариант -- напихать в каждый класс virtual методов, которые будут измерять расстояние от this до другого примитива. Это конечно позволит избавиться от большинства typeid'ов, а можно даже ото всех, но это как-то совсем черезжопу. Примерно так:
Code: Select all
class point {
virtual float distance(primitive *p) {
return p->distance(this);
}
virtual float distance(point *p);
virtual float distance(line_segment *ls);
};
class line_segment {
virtual float distance(primitive *p) {
return p->distance(this);
}
virtual float distance(point *p) {
/* используем коммутативность: */
return p->distance(this);
}
virtual float distance(line_segment *ls);
};
Если у нас N разных примитивов, то придётся N+1 виртуальную функцию засовывать в каждый класс.
Можно сделать двумерный массив указателей на те реализации перегруженной distance. Немного повозиться в рантайме с разруливанием коммутативности, или явно создать по перегруженной функции на каждую пару типов. Самый многообещающий вариант, но, блин, C++ ленив приделывать типам целочисленные идентификаторы, и придётся вместо двумерного массива делать двумерный map, или отдельно преобразовывать строковые идентификаторы типов в небольшие целые числа.
Вот на твой взгляд, о Мастер C++, какой из этих путей менее уродлив? Или лучше пойти C'шным путём? То есть завести целочисленное поле type в каждом классе, и потом свести реализацию generic_distance, к:
Code: Select all
return generic_fn_table[p1->type][p2->type](p1, p2);
Чего-то мне кажется, что C'шный подход выйдет наиболее лаконичным и наименее сбивающим с толку читателя кода.