2016-10-04 2 views
0

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

Я работаю над собственной системой обмена сообщениями, которая предназначена для отправки сообщений (struct) потребителям.

Когда проект хочет использовать систему обмена сообщениями, она будет определять набор сообщений (enum class), типы данных (struct), и отношения между этими объектами:

template <MessageType E> struct expected_type; 
template <> struct expected_type<MessageType::TypeA> { using type = Foo; }; 
template <> struct expected_type<MessageType::TypeB> { using type = Bar; }; 
template <> struct expected_type<MessageType::TypeM> { using type = Foo; }; 

Обратите внимание, что различные типы сообщения могут использовать один и тот же тип данных.

Код для отправки этих сообщений обсуждается в моем предыдущем вопросе. Существует один шаблонный метод, который может отправлять любое сообщение и поддерживает безопасность типов с использованием определений шаблонов выше. Он работает очень хорошо.

Мой вопрос касается сообщения приемник класс. Существует базовый класс, который реализует методы, подобные этим:

ReceiveMessageTypeA(const Foo & data) { /* Some default action */ }; 
ReceiveMessageTypeB(const Bar & data) { /* Some default action */ }; 
ReceiveMessageTypeM(const Foo & data) { /* Some default action */ }; 

Затем он реализует одну функцию обработки сообщений, например:

bool ProcessMessage(MessageType msgType, void * data) { 
    switch (msgType) { 
    case TypeA: 
     ReceiveMessageTypeA(data); 
     break; 

    case TypeB: 
     ReceiveMessageTypeB(data); 
     break; 

    // Repeat for all supported message types 

    default: 
     // error handling 
     break; 
    } 
} 

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

Боковое примечание: игнорируйте тот факт, что я передаю void *, а не конкретный тип. Есть еще несколько кодов между ними, чтобы обрабатывать все это, но это не является необходимой детальностью.

Проблема с подходом заключается в добавлении нового типа сообщения. Помимо определения специализации enum, struct и expected_type<>, базовый класс должен быть изменен, чтобы добавить новый метод по умолчанию ReceiveMessageTypeX, а оператор switch в функции ProcessMessage должен быть обновлен.

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


Вот моя попытка решения:

В базовом классе определить метод:

template <MessageType msgType> 
bool Receive(expected_type<msgType>::type data) { 
    // Default implementation. Print "Message not supported", or something 
} 

Затем подклассы могут просто реализовать специализаций, они заботятся о:

template<> Receive<MessageType::TypeA>(const Foo & data) { /* Some processing */ } 
// Don't care about TypeB 
template<> Receive<MessageType::TypeM>(const Foo & data) { /* Some processing */ } 

Я думаю, что решает часть проблемы; Мне не нужно определять новые методы в базовом классе.

Но я не могу понять, как избавиться от оператора switch.Я хотел бы быть в состоянии сделать это:

bool ProcessMessage(MessageType msgType, void * data) { 
    Receive<msgType>(data); 
} 

Это не будет делать, конечно, потому что шаблоны не работают, как это.

Вещи я подумал:

  1. Создание оператора коммутатора от expected_type структуры. Я не знаю, как это сделать.
  2. Поддержание какой-либо карты указателей функций и вызов нужного. Проблема в том, что я не знаю, как инициализировать карту, не повторяя данные от expected_type, что я не хочу делать.
  3. Определение expected_type с использованием макроса, а затем воспроизведение препроцессорных игр для массажа этих данных в оператор switch. Это может быть жизнеспособным, но я стараюсь избегать макросов, если это возможно.

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

Я могу изменить expected_type при необходимости, если он не сломает мой метод Send (см. Мой other question).

ответ

1

У вас была правильная идея с expected_type и Receive шаблонами; осталось всего один шаг, чтобы все это работало.

Во-первых, мы должны дать нам какие-то средства, чтобы перечислить через MessageType:

enum class MessageType { 
    _FIRST = 0, 
    TypeA = _FIRST, 
    TypeB, 
    TypeM = 100, 
    _LAST 
}; 

И тогда мы можем перечислить через MessageType во время компиляции и создания диспетчерских функций (с использованием SFINAE, чтобы пропустить значения не определены в expected_types):

// this overload works when expected_types has a specialization for this value of E 
template<MessageType E> void processMessageHelper(MessageType msgType, void * data, typename expected_type<E>::type*) { 
    if (msgType == E) Receive<E>(*(expected_type<E>::type*)data); 
    else processMessageHelper<(MessageType)((int)E + 1)>(msgType, data, nullptr); 
} 
template<MessageType E> void processMessageHelper(MessageType msgType, void * data, bool) { 
    processMessageHelper<(MessageType)((int)E + 1)>(msgType, data, nullptr); 
} 
template<> void processMessageHelper<MessageType::_LAST>(MessageType msgType, void * data, bool) { 
    std::cout << "Unexpected message type\n"; 
} 

void ProcessMessage(MessageType msgType, void * data) { 
    processMessageHelper<MessageType::_FIRST>(msgType, data, nullptr); 
} 
+1

Это может сработать, но MessageType не последовательный. Имеются пробелы, позволяющие определять будущие типы сообщений без нарушения существующих индексов. – endorph

+0

Отредактированный пример, чтобы справиться с этим. –

0

Ваше название говорит: «Вызов различных функций шаблона специализации на основе времени выполнения значение»

Это можно сделать только с помощью своего рода инструкции switch или с функциями virtual.

С одной стороны, это выглядит так, как будто вы делаете объектно-ориентированное программирование, но у вас еще нет virtual методов. Если вы обнаружите, что повсюду пишете псевдообъекты, но у вас нет никаких функций virtual, значит, вы не делаете ООП. Это не плохо. Если вы злоупотребляете ООП, вы можете не оценить конкретные случаи, когда это полезно, и поэтому это просто вызовет больше путаницы.

Упростить код, и не отвлекайтесь на ООП


Вы хотите, чтобы объект типа сообщения, чтобы иметь некоторую «магию», связанный с ним, где он MessageType управляет тем, как он послан. Это означает, что вам нужна виртуальная функция.

struct message { 
    virtual void Receive() = 0; 
} 

struct message_type_A : public message { 
     virtual void Receive() { 
      .... 
     } 
} 

Это позволяет, в случае необходимости, передать эти объекты как message& и вызвать msg.process_me()

+0

Я не уверен, что это что-то мне приносит. Я получаю кусок памяти ('void *') и тип сообщения. Память ограничена как POD, что означает отсутствие виртуальных функций-членов. Если бы я хотел преобразовать его в объект 'message', мне все равно понадобился бы оператор switch некоторого типа, чтобы выяснить, какой тип он есть. – endorph

Смежные вопросы