Предположим, у меня есть библиотека с сотнями таких макросов:
#define EVENT_X 0
#define EVENT_XYZ 1
#define EVENT_123 2
...
Я хотел бы предложить пользователю библиотеки способ доступа к этим макросам по имени. Я не знаю, как это сделать, учитывая, что имена макросов исчезли к моменту завершения препроцессора.
Конкретно: как я могу создать функцию event_name(int event_value), которая отображает имя события, чтобы:
event_name(EVENT_X);
event_name(1);
дисплеи
"X"
"XYZ"
Я могу сделать это неэлегантным и подверженным ошибкам способом, определив массив в своей библиотеке, где я определяю имя каждого из этих макросов вручную:
const char* EVENT_NAMES[] = {
"X",
"XYZ",
"123",
... }
в этом случае моя функция event_name может быть просто:
void event_name(int event_value) {
printf("%s\n",EVENT_NAMES[event_value]);
}
Но с сотнями макросов создавать вручную этот массив EVENT_NAMES сложно и подвержено ошибкам.
Несколько более надежным решением является создание этого массива следующим образом:
const char* EVENT_NAMES[] = {
STRINGIFY(EVENT_X),
STRINGIFY(EVENT_XYZ),
...
}
с #define STRINGIFY(X) #x
Но это означает удаление во время выполнения «EVENT_», и мне по-прежнему нужно повторно перечислять все макросы вручную при создании EVENT_NAMES, рискуя забыть некоторые макросы или переставить их.
Есть ли лучшее решение?
ваш первый способ - это то, как это реально делается
Если есть пробелы (например, нет события 5, но есть 4 и 6), вы можете сделать это так const char *EVENT_NAMES[] = { [EVENT_X] = STRINGIFY(EVENT_X), [EVENT_Y] = STRINGIFY(EVENT_Y) };
Должны ли они использовать макросы препроцессора #defined или перечисления будут работать?
Являются ли значения определений только целыми числами? Являются ли они последовательными, или с редкими и крошечными отверстиями, или сколько угодно? В зависимости от этого может быть решение, определяющее «одновременно» некоторые значения перечисления и массив для преобразования в читаемый пользователем текст.
Вы делаете это, как в вашем первом описании, но вы не делаете это «вручную». Ваша система сборки генерирует массив для вас.
почему вы не можете использовать static map <int, const char*> вместо того, чтобы считать индекс массива значением макроса. Вы можете заполнить эту карту при запуске, и она также не будет подвержена ошибкам.
@Yogesh Потому что: неправильный язык программирования. Кроме того, даже если C++ был вариантом, то std::map использует выделение кучи во время выполнения и, как правило, чертовски медленный по сравнению с макросами времени компиляции и LUT прямого доступа.
вам нужна логика за горизонтом макросов - поэтому, если вы настаиваете на том, чтобы делать это с помощью макросов, я думаю, вы проиграли. Но если нет, ваша библиотека может предоставить ENUM, содержащий все значения (почти как макросы для пользователя) и обернуть создание значений перечисления макросом CREATE_EVENT(имя,значение) в заголовочном файле для пользователя и другим реализация этого же макроса для внутреннего использования библиотек (опять же источник файла включения библиотеки), чтобы действительно дополнительно создать назначения строк, например. в библиотеке init().
Ошибки красоты - это предупреждения о переопределении этого макроса при компиляции вашей библиотеки, и, возможно, ваш пользователь мог получить двойные символы (играл в это до конца). Возможно, есть способ в сочетании с typedef перечисления также избежать дублирования символов. но, по крайней мере, вы определяете значения только один раз и обходите несоответствия.





Учитывая, что здесь нет необходимости иметь #define, а значения событий можно рассматривать как маленькие последовательные целые числа.
В C невозможно одновременно определить значения перечисления и массив, описывающий эти значения.
Но определить все без риска несоответствий все же возможно.
У нас есть препроцессор, который работает с текстами и может читать файлы.
Сначала создайте файл с именем, например, «events.inl» (на самом деле это не заголовок или файл кода), файл должен содержать только такие строки, как:
DEF_EVENT( X, 0 ) // EVENT_ prefix must not be here
DEF_EVENT( XYZ, 1 )
DEF_EVENT( 123, 12 )
Затем мы добавим этот файл, чтобы сгенерировать перечисление где-то и массив где-то еще:
// define events as enum, all events are named EVENT_...
//======================
#define DEF_EVENT(name,value) EVENT_##name = value,
enum {
#include "events.inl"
};
#undef DEF_EVENT
// the array for user access
//==========================
#define DEF_EVENT(name,value) [value] = #name,
const char* const EVENT_NAMES[] = {
#include "events.inl"
};
#undef DEF_EVENT
Если для значения нет события, в массиве есть указатель NULL.
Определения могут быть в любом порядке, и размер массива по-прежнему приемлем, если значения представляют собой небольшие неотрицательные числа.
Редактировать : Мы можем избежать файла, используя большой макрос, и просто заменить #include(s) этим макросом:
#define ALL_DEF_EVENTS \
DEF_EVENT( X, 0 ) \
DEF_EVENT( XYZ, 1 ) \
DEF_EVENT( 123, 12 )
Это довольно аккуратно, дважды определить DEF_EVENT с разными значениями. Я посмотрю, появятся ли другие ответы, но на первый взгляд мне нравится то, что вы предлагаете.
Техника называется x-macros. Вы можете поискать это. Википедия X Macro например.
@JonathanLeffler, спасибо за ссылку. Внутри мы можем увидеть, как просто адаптировать мой образец, чтобы избежать этого неприятного промежуточного файла.
Как насчет использования перечисления вроде
enum EVENT {X, XYZ};, а затем простоSTRINGIFY(XYZ);?