Как оптимально перевести перечисления в наименьшее количество кода? - PullRequest
3 голосов
/ 07 июля 2010

Часто бывает, что у вас есть 2 перечисления, которые эквивалентны, но пронумерованы по-разному, и вам нужны функции для перевода элементов из первого перечисления в элементы второго и обратного.Обычно это действительно утомительно для кода, и каждый раз, когда добавляется новый элемент, вы должны обязательно добавить сопряжение как в функцию преобразования, так и в функцию обратного преобразования (это нарушает принцип СУХОГО ).

Какой наименее подверженный ошибкам способ сделать это, который все еще генерирует эффективный код?Я упомянул эффективную часть, потому что вы могли бы просто сделать кучу пар и поместить их в std :: maps, если поиск во время выполнения не был проблемой.Я бы предпочел что-то такое же производительное, как ручное написание больших операторов switch, переходящих от одного значения перечисления к другому.

С некоторой магией повышения препроцессора или с некоторыми хакерскими шаблонами, я уверен, что вы могли бы придумать что-то, гденаписать список пар и генерировать функции преобразования и обратного преобразования, но я не уверен, какой подход предпочтительнее или почему.Оба метода имеют репутацию медленного времени компиляции и трудно диагностировать ошибки компиляции.Или, может быть, какой-то другой подход целиком?

Ответы [ 6 ]

2 голосов
/ 07 июля 2010

Как сказал Нил, я никогда не сталкивался с проблемой сам.Хотя я столкнулся с проблемой кодовых наборов (т.е. сопоставление строки с перечислением и обратно).

Моя первая реакция - эпидермальная: DRY.

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

Моя вторая реакция - попытаться удалить перечисление.Я не люблю перечисления.Возможно, я буду признателен за них с выходом C ++ 0x, но в их нынешнем виде они доставляют больше хлопот, чем они того стоят.Я предпочитаю умные объекты с категориями и т. Д.

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

Что вы хотели бы написать:

DEFINE_CORRESPONDING_ENUMS(Server, Client,
  ((Server1, 1, Client1, 6))
  ((Server2, 2, Client2, 3))
  ((Common1, 4, Common1, 4))
  ((Common2, 5, Common2, 5))
  ((Server3, 7, Client3, 1))
);

И мы бы хотели, чтобы оно генерировало:

struct Server
{
  enum type { Server1 = 1, Server2 = 2, Common1 = 4, Common2 = 5, Server3 = 7 };
};

struct Client
{
  enum type { Client1 = 6, Client2 = 3, Common1 = 4, Common2 = 5, Client3 = 1 };
};

Server::type ServerFromClient(Client::type c)
{
  switch(c)
  {
  case Client1: return Server1;
  //...
  default: abort();
  }
}

Client::type ClientFromServer(Server::type s)
{
  //...
}

Хорошая новость в том, что это возможно.Я мог бы даже сделать это, хотя я, вероятно, позволю вам немного поработать над этим;)

Вот некоторые объяснения:

  • Третий элемент макроса - это последовательность.Последовательность имеет неограниченный размер.
  • Каждый элемент последовательности представляет собой 4-кортеж.Вы должны знать его размер заранее, таким образом, повторение для Common.Если бы вы использовали последовательность вместо этого, вы могли бы иметь дело с переменным количеством элементов (например, чтобы избежать повторения общего ...), но это усложнило бы ситуацию
  • Вам нужно будет взглянуть на BOOST_PP_SEQ_FOREACH, здесь это будет основная операция.
  • Не забудьте BOOST_PP_CAT для обработки конкатенации токенов.
  • на gcc опция -E дает выход препроцессора, это может пригодиться ...
  • не забывайте комментарии и как использовать в файле, ваши коллеги будут ненавидеть вас в противном случае
2 голосов
/ 07 июля 2010

Почему справочная таблица не работает?Почему вы вынуждены использовать этот гигантский оператор переключения ??

2 голосов
/ 07 июля 2010

Вы ищете что-то подобное?Не проверено, но должно работать.

(применяются стандартные предупреждения о преждевременной оптимизации и необходимости профилирования; поиск в std :: map может быть не таким уж плохим, гигантская таблица переключателей может быть не такой уж хорошей).

enums-impl.h:

// No include guard.
DEFINE_ENUM_PAIR(EGA_BRIGHT_RED, 12, HTML_RED, 0xff0000)
DEFINE_ENUM_PAIR(EGA_BRIGHT_BLUE, 9, HTML_BLUE, 0x0000ff)
DEFINE_ENUM_PAIR(EGA_BRIGHT_GREEN, 10, HTML_GREEN, 0x00ff00)
DEFINE_ENUM_PAIR(EGA_BLACK, 0, HTML_BLACK, 0x000000)

enums.cpp:

enum EgaColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name1 = value1,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
};

enum HtmlColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name2 = value2,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR 
};

HtmlColorType ConvertEgaToHtml(EgaColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name1: return name2;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}

EgaColorType ConvertHtmlToEga(HtmlColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name2: return name1;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}
1 голос
/ 07 июля 2010

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

enum A
{
    MIN_A = 1,
    A_ATT_1 = 1,
    A_ATT_2 = 2,
    A_ATT_3 = 3,
    LAST_A
};

enum B
{
    MIN_B = 2
    B_ATT_2 = 2,
    B_ATT_1 = 4,
    B_ATT_3 = 5,
    LAST_B
};

B A_to_B[] =
{
    B_ATT_1,
    B_ATT_2,
    B_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_A - MIN_A == sizeof(A_to_B) / sizeof(A_to_B[0]);

B from_A(A in)
{
    B ret = A_to_B[in - MIN_A];
    assert(ret != LAST_B);
    return ret;
}

A B_to_A[] =
{
    A_ATT_2,
    LAST_A,
    A_ATT_1,
    A_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_B - MIN_B == sizeof(B_to_A) / sizeof(B_to_A[0]);

A from_B(B in)
{
    A ret = B_to_A[in - MIN_B];
    assert(ret != LAST_A);
    return ret;
}
0 голосов
/ 07 июля 2010

Не используйте два перечисления.

Нет большой разницы между этими значениями:

enum FirstSet { A=4, B=6, C=8, D=5 };
enum SecondSet { E=2, F=5, G=5, H=1 };

и этим:

enum OneEnum { A, B, C, D };
enum TwoEnum { E, F, G, H };
int FirstSet[] = { 4, 6, 8, 5 };
int SecondSet[] = { 2, 5, 5, 1 };

Количество обращений, которые необходимо изменитьможет быть непосильным, но это немного лучше, чем поиск O (n) каждый раз, когда вы хотите конвертировать.

0 голосов
/ 07 июля 2010

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

...