Есть ли причина, по которой вы не используете динамическую диспетчеризацию для функции computecost
?
Самое простое было бы создать иерархию наследования и просто использовать динамическую диспетчеризацию. Каждый тип в иерархии, который будет возвращать mxINT8_CLASS
в качестве идентификатора класса, будет реализовывать computecost
в качестве вызова computecost<signed char>
, и аналогично для всех других комбинаций.
Если есть веская причина не использовать динамическую диспетчеризацию, вы можете рассмотреть возможность реализации собственной динамической диспетчеризации различными способами. Самое очевидное, простое и, вероятно, более простое в обслуживании - это то, что у вас уже есть. Чуть сложнее можно сделать с помощью макросов, или вы можете попробовать шаблонную версию просто для удовольствия ...
Макро-решение (следующее по сложности) может использовать макрос для определения отношения, другой для определения каждого case
, а затем объединить их:
#define FORALL_IDS( macro ) \
macro( mxINT8_CLASS, signed char ); \
macro( mxUINT8_CLASS, unsigned char ); \
// ...
#define CASE_DISPATCH_COMPUTECOST( value, type ) \
case value: computecost<type>( T, offT, Offset, CostMatrix ); break
Объединить:
switch ( category ) {
FORALL_IDS( CASE_DISPATCH_COMPUTECOST );
};
Я видел, как это делалось в прошлом, и мне это не нравится, но если есть много мест, где вам нужно сопоставить категорию с типом, который может быть простым, чтобы написать трудное в обслуживании решение , Также обратите внимание, что макрос FORALL_IDS
может использоваться для реализации признаков метапрограммирования, которые отображаются из перечисления в тип и наоборот:
template <classId id>
struct type_from_id;
#define TYPE_FROM_ID( id, T ) \
template <> struct type_from_id<id> { typedef T type; }
FORALL_IDS( TYPE_FROM_ID );
#undef TYPE_FROM_ID
template <typename T>
struct id_from_type;
#define ID_FROM_TYPE( id, T ) \
template <> struct id_from_type<T> { static const classId value = id; }
FORALL_IDS( ID_FROM_TYPE );
#undef ID_FROM_TYPE
Обратите внимание, что у этого есть много недостатков: макросы по своей природе небезопасны, а это макросов в большей степени, так как они определяют типы и не совсем ведут себя как функции, труднее найти соответствующее количество скобки к аргументам, что делает его более подверженным кратковременным ошибкам в подстановке текста ... Макросы не знают о контекстах, поэтому вы можете попытаться минимизировать область действия, определяя их сразу после использования. Реализация описанных выше черт является одним хорошим способом решения этой проблемы: создайте макрос, используйте его для генерации шаблонного немарокодового кода, отмените макрос. Остальная часть кода может использовать шаблоны вместо макросов для сопоставления одного с другим.
Другим способом реализации динамической диспетчеризации является использование таблицы поиска вместо приведенного выше оператора switch
:
typedef T function_t( T1, T2, T3 ); // whatever matches the `computecost` signature
function_t *lookup[ categories ]; // categories is 1+highest value for the category enum
Вы можете вручную инициализировать таблицу поиска или использовать макрос, как указано выше, сложность кода не сильно изменится, просто перейдите от вызывающей стороны туда, где инициализирована таблица поиска. На стороне вызывающего абонента вы бы просто сделали:
lookup[ mxGetClassID(prhs) ]( T, offT, Offset, CostMatrix );
Вместо оператора switch
, но не дайте себя одурачить, стоимость не будет удалена, просто подтолкнута к инициализации (что может быть хорошо, если вам нужно сопоставить более одной функции, так как вы можете создать структурировать указатели на функции и выполнять инициализацию всех сразу, и там у вас есть свой собственный vtable
, настроенный вручную, где вместо vptr
вы используете поле classId
для индексации.
Шаблонная версия этого, пожалуй, самая громоздкая. Я попытался бы реализовать это просто для удовольствия, но не использовал его в производственном коде. Вы можете попробовать создать таблицу поиска по шаблону [1] , что забавно как упражнение, но, вероятно, более сложное, чем первоначальная задача.
В качестве альтернативы вы можете реализовать тип-подхода тип подхода (A la Modern C ++ Design ) и в каждом из узлов отправлять на соответствующая функция. Это, вероятно, не стоит затрат и в будущем станет кошмаром для обслуживания, поэтому держитесь подальше от производственного кода.
Подводя итог:
Просто используйте динамическую диспетчеризацию языка, это ваш лучший вариант. Если есть не слишком веская причина, сбалансируйте различные варианты и сложности. В зависимости от того, сколько мест вам нужно выполнить диспетчеризацию от classId до X (где X здесь computecost
, но может быть гораздо больше), рассмотрите возможность использования ручной таблицы поиска, которая будет инкапсулировать все операции X
в таблицу функций - отметим, что в этот момент мотивы избегания vtable
могли бы исчезнуть: вы вручную и подвержены ошибкам реализовали одного и того же зверя!
[1] Сложность в этом случае несколько выше из-за отображения перечисления в типы, но она не должна быть намного более сложной.