Как задать соответствие числа и типа данных C++?
От внешнего источника в двоичном виде приходят данные, как можно в зависимости от кода типа данных получать переменную определёного типа, возможно ли это сделать более обобщённо без множества условий? Ниже привожу пример текущей реализации.
// Классы, реализующие обработку сообщений
class MsgType1;
class MsgType2;
class MsgType3;
class MsgType4;
enum class MsgCode {
Type1,
Type2,
Type3,
Type4
};
void data_reseive(int msg_type, void* msg_data) {
MsgCode c = static_cast<MsgCode>(msg_type);
switch (c) {
case MsgCode::Type1:
{
MsgType1 msg;
msg.process(msg_data);
break;
}
case MsgCode::Type2:
{
MsgType2 msg;
msg.process(msg_data);
break;
}
case MsgCode::Type3:
{
MsgType3 msg;
msg.process(msg_data);
break;
}
case MsgCode::Type4:
{
MsgType4 msg;
msg.process(msg_data);
break;
}
}
}
// Пример с использованием полиморфизма
class MsgTypeBase {
public:
virtual void process(void* data) = 0;
virtual bool operator==(MsgCode code) = 0;
};
template<MsgCode C>
class MsgTypeContBase: MsgTypeBase {
public:
bool operator==(MsgCode code) {
return static_cast<int>(C) == static_cast<int>(code);
}
};
class MsgTypeCont1: public MsgTypeBase<MsgCode::Type1> {
public:
void process(void* data) {
MsgType1 msg;
msg.process(data);
}
};
class MsgTypeCont2: public MsgTypeBase<MsgCode::Type2> {
public:
void process(void* data) {
MsgType2 msg;
msg.process(msg_data);
}
};
class MsgTypeCont3: public MsgTypeBase<MsgCode::Type3> {
public:
void process(void* data) {
MsgType3 msg;
msg.process(msg_data);
}
};
class MsgTypeCont4: public MsgTypeBase<MsgCode::Type4> {
public:
void process(void* data) {
MsgType4 msg;
msg.process(msg_data);
}
};
std::vector<MsgTypeBase*> m_list;
void init() {
m_list.push_back(new MsgTypeCont1());
m_list.push_back(new MsgTypeCont2());
m_list.push_back(new MsgTypeCont3());
m_list.push_back(new MsgTypeCont4());
}
void data_reseive(int msg_type, void* msg_data) {
MsgCode c = static_cast<MsgCode>(msg_type);
for (auto m: m_list) {
if (*m == c) {
m->process(msg_data);
return;
}
}
}
Ответы (4 шт):
Так сделать не получится, поскольку Ваш тип данных MsgType зависит строго от того, какие данные были получены
На крайняк можно написать какой-нибудь класс, инкапсилурующий внутри себя все эти проверки и скрывающий реализацию выведения типа MsgType, но реализация, которая у Вас, на самом деле не является костыльной, можно и так оставить.
Если идентификаторы идут подряд, то можно сделать диспечеризацию по списку типов, если нет (или если их слишком много для перебора) - то по аналогии через таблицу соответствия:
#include <type_traits>
#include <tuple>
#include <utility>
using
t_MsgCode = int;
template
<
typename x_Msg
>
void
Process
(
void * const data
);
template
<
typename x_MsgsTuple
, typename x_IdsSequence = void
>
struct
t_Dispatcher;
template
<
typename... x_Msgs
, t_MsgCode... x_indexes
>
struct
t_Dispatcher<::std::tuple<x_Msgs...>, ::std::integer_sequence<t_MsgCode, x_indexes...>> final
{
static void
Dispatch
(
t_MsgCode const code
, void * const p_data
)
{
(((code == x_indexes) ? Process<x_Msgs>(p_data) : void()), ...);
}
};
template
<
typename... x_Msgs
>
struct
t_Dispatcher<::std::tuple<x_Msgs...>, void> final
{
static void
Dispatch
(
t_MsgCode const code
, void * const p_data
)
{
using
t_IdsSequence = ::std::make_integer_sequence
<
t_MsgCode
, static_cast<t_MsgCode>(sizeof...(x_Msgs))
>;
t_Dispatcher<::std::tuple<x_Msgs...>, t_IdsSequence>::Dispatch(code, p_data);
}
};
struct
t_FirstMsg {};
struct
t_SecondMsg {};
struct
t_ThirdMsg {};
using
t_Msgs = ::std::tuple<t_FirstMsg, t_SecondMsg, t_ThirdMsg>;
void
Receive_Data
(
t_MsgCode const code
, void * const p_data
)
{
t_Dispatcher<t_Msgs>::Dispatch(code, p_data);
}
Написал свой вариант.
// Функции, реализующие обработку сообщений
// имена могут быть любые
void process_msg1(void* data) {}
void process_msg2(void* data) {}
void process_msg3(void* data) {}
void process_msg4(void* data) {}
// сигнатура осталась старой
void data_reseive(int msg_type, void* msg_data) {
// эта мапа помечена static, что бы один раз инициализировать
static std::map<int, void(*)(void* data)> trans =
{
// привязываем идекс-обработчик
{1, &process_msg1},
{2, &process_msg2},
{3, &process_msg3},
{4, &process_msg4}
};
auto f = trans.find(msg_type);
if (f == trans.end()) {
// ой, ничего не найдено
return;
}
// собственно вызываем обработку.
f->second(msg_data);
}
Если индексы идет подряд, можно использовать массив. Это даст чуточку ускорения. Мапу с функции можно при желании вынести. А вообще при желании можно там вставить лямбды, но это на любителя
Я в Си делал виртуальную таблицу. Создал массив указателей на функции. И вызываю по коду. Для вашего примера примерно так:
enum class MsgCode {
Type1,
Type2,
Type3,
Type4
};
typedef void (*functype)(void*);
extern functype virtTable [ ] ;
void data_reseive(int msg_type, void* msg_data) {
functype f = virtTable [ msg_type ] ;
f ( msg_data ) ; }
Для каждого типа отдельная функция :
void virtfunc1 ( void * msg_data ) {
MsgType1 msg;
msg . process( msg_data ); }
Адреса функций надо будет где-то проставить :
functype virtTable [ ] = { virtfunc1 , virtfunc2 ,
virtfunc3 , virtfunc4 } ;
KoVadim примерно похожее показал. Но у меня Си стиль.