Я пишу код в среде, написанной на C и несовместимой с C++. При реализации структуры данных для наборов в виде двусвязных списков я использовал подобные макросы для создания дженериков.
#pragma once
/* Interface protocol:
* 1. Do not modify contents of nodes in a list. Results should be regarded
* as returned instead of modified in the arguments. The old list shall
* not be used.
* 2. Use RA_DECL_DL (T) to declare the doubly linked list and related oper
* for type T. If RA_DECL_DL (T) is used in an `.h' file, put it in
* #define GUARD_RA_FL_<T> #ifdef GUARD_RA_FL_<T> ... #endif
* to prevent re-declarations.
* 3. A function int _<T>_eq (T x, T y) must be declared in that file, before
* RA_DECL_DL and RA_DEFN_DL.
* 4. An RA_DL (T) can be used both as a set or a doubly linked list, but not
* simultaneously for a particular instance. */
#define RA_DECL_DL(T) \
typedef struct _##T##_DL_ *T##_DL_t; \
struct _##T##_DL_ \
{ \
T val; \
T##_DL_t next; \
T##_DL_t prev; \
}; \
T##_DL_t _##T##_DL_empty (); \
T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst); \
T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst); \
int _##T##_DL_isIn (T x, T##_DL_t lst); \
int _##T##_DL_isEmpty (T##_DL_t lst); \
T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2); \
T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2); \
T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2);
#define RA_DEFN_DL(T) \
T##_DL_t _##T##_DL_empty () { return NULL; } \
T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst) \
{ \
T##_DL_t ret = checked_malloc (sizeof (struct _##T##_DL_)); \
if (lst == NULL) \
{ \
ret->val = x; \
ret->next = NULL; \
ret->prev = NULL; \
return ret; \
} \
else \
{ \
for (T##_DL_t iter = lst; iter != NULL; iter = iter->next) \
{ \
if (_##T##_eq (iter->val, x)) \
return lst; \
} \
ret->val = x; \
ret->prev = NULL; \
ret->next = lst; \
lst->prev = ret; \
return ret; \
} \
} \
T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst) \
{ \
T##_DL_t iter; \
for (iter = lst; iter != NULL; iter = iter->next) \
{ \
if (_##T##_eq (iter->val, x)) \
break; \
} \
if (iter == NULL) \
return lst; \
if (iter->prev != NULL) \
iter->prev->next = iter->next; \
if (iter->next != NULL) \
iter->next->prev = iter->prev; \
return iter->prev == NULL ? lst->next : lst; \
} \
int _##T##_DL_isIn (T x, T##_DL_t lst) \
{ \
for (T##_DL_t iter = lst; iter != NULL; iter = iter->next) \
{ \
if (_##T##_eq (iter->val, x)) \
return 1; \
} \
return 0; \
} \
T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2) \
{ \
T##_DL_t ret = _##T##_DL_empty (); \
for (T##_DL_t iter = l1; iter != NULL; iter = iter->next) \
ret = _##T##_DL_insert (iter->val, ret); \
for (T##_DL_t iter = l2; iter != NULL; iter = iter->next) \
ret = _##T##_DL_insert (iter->val, ret); \
return ret; \
} \
T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2) \
{ \
T##_DL_t ret = _##T##_DL_empty (); \
for (T##_DL_t iter = l1; iter != NULL; iter = iter->next) \
{ \
if (_##T##_DL_isIn (iter->val, l2)) \
ret = _##T##_DL_insert (iter->val, ret); \
} \
return ret; \
} \
T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2) \
{ \
T##_DL_t ret = _##T##_DL_empty (); \
for (T##_DL_t iter = l1; iter != NULL; iter = iter->next) \
{ \
if (!_##T##_DL_isIn (iter->val, l2)) \
ret = _##T##_DL_insert (iter->val, ret); \
} \
return ret; \
}
#define RA_DL(T) T##_DL_t
#define RA_DL_empty(T) _##T##_DL_empty ()
#define RA_DL_insert(T, x, lst) _##T##_DL_insert (x, lst)
#define RA_DL_remove(T, x, lst) _##T##_DL_remove (x, lst)
#define RA_DL_isIn(T, x, lst) _##T##_DL_isIn (x, lst)
#define RA_DL_union(T, l1, l2) _##T##_DL_union (l1, l2)
#define RA_DL_intersect(T, l1, l2) _##T##_DL_intersect (l1, l2)
#define RA_DL_diff(T, l1, l2) _##T##_DL_diff (l1, l2)
Проблема в следующем: если для двух файлов, скажем f1.c и f2.c, я пишу RA_DECL_DL(int) и RA_DEFN_DL(int) в обоих файлах, то, например, функция _int_DL_insert определена дважды глобально. Это вызывает конфликт между идентификаторами, хотя на самом деле они идентичны.
Использование защитных макросов #ifdef не решает эту проблему, поскольку макросы не видны в файлах .c. Использование static не позволяет мне использовать функции в нескольких файлах, что иногда необходимо.
Какие наборы инструментов вы должны поддерживать или требуется полностью переносимый C?
@MikeKinghan Полностью портативный и стандартный C, не более поздняя версия, чем C99.
Используйте макрос RA_DECL_DL там, где вам нужны видимые функции. Используйте макрос RA_DEFN_DL в одном файле для каждого типа. Правила такие же, как если бы вы написали код без макросов. Будьте осторожны при использовании имен, начинающихся с подчеркивания.





Я согласен с @Lundin, что это слишком много макросов. Я уверен, что вы знаете, что общая реализация с использованием шаблонов C++ проста — контейнер шаблонов. наберите std::set готов - но вы говорите свой Фреймворк «несовместим» с C++. Однако C++, создающий экземпляр шаблона реализация может предоставить интерфейс связи C с помощью extern C {....} спецификатор языковой связи , и вы Чтобы обеспечить безопасность ABI, нужно только уметь использовать одну и ту же цепочку инструментов для C и C++.
Если связь C++ с C по какой-то причине исключена, предположим, что ваш заголовочный файл макроса — DL_macros.h.
Если вы собираетесь контролировать инвентарь типов, для которых предназначены ваши макросы экземпляр, то вы можете сделать следующее.
Если вы хотите, чтобы пользователь ваших макросов мог использовать их в непредвиденных
типы, то вы можете указать README, который подскажет им сделать следующее.
Для каждого типа Type, для которого вы хотите создать экземпляр макроса,
создайте заголовочный файл с подходящим именем, DL_Type.h например:
#pragma once
#include <Type.h> // Maybe
#include <DL_macros.h>
int _Type_eq (Type x, Type y); // Per your protocol item #3
RA_DECL_DL(Type)
где Type.h будет типом определения соответствующего заголовочного файла Type, если это определенный, а не фундаментальный тип.
Также создайте исходный файл DL_Type.c, например:
#include <DL_Type.h>
int _Type_eq (Type x, Type y)
{
return x == y; // Or whatever is right.
}
RA_DEFN_DL(Type)
Затем скомпилируйте файл DL_Type.c в объектный файл DL_Type.o. Свяжите этот объектный файл
с клиентским кодом, которому требуется API, объявленный в DL_Type.h. Такой код будет #include <DL_Type.h>.
Клиентский код будет ссылаться на уникальные определения, представленные в DL_Type.o.
При желании вы или ваши пользователи можете создать статическую библиотеку из объектных файлов. создание экземпляров определений макросов для различных типов. Из такой статической библиотеки компоновщик будет связывать только объектные файлы-члены, на которые ссылается клиентский код.
Вы также можете избежать предоставления макроса RA_DEFN_DL(T) клиентскому коду, поместив
макрос RA_DECL_DL(T) в заголовке, обращенном к клиенту, скажем, DL_macros_decl.h,
поместив RA_DEFN_DL(T) в другой, скажем, DL_macros_defn.h и включив
последний только в DL_Type.c:
#include <DL_Type.h>
#include <DL_macros_defn.h>
При желании вы также можете предоставить условную поддержку для цепочек инструментов, таких как GCC,
которые распознают слабые символы. Компоновщик
будет терпеть несколько определений слабого символа и произвольно выбирать одно из них (на практике
первый увиденный), поэтому для этих цепочек инструментов это будет ненужно ни вам, ни вашим пользователям.
скомпилировать и связать отдельный DL_Type.o для каждого Type: вы могли бы RA_DECL_DL(Type) и
RA_DEFN_DL(Type) в той же единице перевода, как вы изначально предполагали, если
думаю, что это очень желательно.
Для этого варианта DL_macros.h будет изменен в строках:
#ifdef __GNUC__
#define WEAK __attribute__((weak))
#else
#define WEAK
#endif
#define RA_DECL_DL(T) \
typedef struct _##T##_DL_ *T##_DL_t; \
struct _##T##_DL_ \
{ \
T val; \
T##_DL_t next; \
T##_DL_t prev; \
}; \
T##_DL_t _##T##_DL_empty () WEAK; \
T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst) WEAK; \
T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst) WEAK; \
int _##T##_DL_isIn (T x, T##_DL_t lst) WEAK; \
int _##T##_DL_isEmpty (T##_DL_t lst) WEAK; \
T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2) WEAK; \
T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2) WEAK; \
T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2) WEAK;
и требуемое объявление для функции _Type_eq будет иметь вид
int _Type_eq (Type x, Type y) WEAK;
Я бы предпочел не заморачиваться с дифференциацией цепочек инструментов.
которые распознают слабые символы и повсеместно придерживаются составления четкого
DL_Type.o.
Предостережение @Jonathan Leffler по поводу использования имен, начинающихся с подчеркивания относится к тому факту, что имена, начинающиеся с двойного подчеркивания или символа подчеркивание и заглавная буква зарезервированы разработчиком компилятора в C. Ваши макросы могут давать имена которые нарушают эту оговорку.
@KaguraHitoha P.S. Если вы исследуете extern C {...} связывание в C++ для своего решения, вам нужно будет научиться явно создавать экземпляр шаблона функции. Посмотрите это и это
Все это очень плохая практика... но есть лучший (менее плохой) способ сделать это с помощью X-макросов, если вас устраивает хранение централизованного списка всех разрешенных типов шаблонов в проекте (?). Вам придется добавлять/удалять типы в этот список вручную, но оттуда он может автоматически генерировать все остальное и без коллизий, поскольку для каждого типа будет ровно 1 объявление и 1 определение. Будет ли это работать или должно работать аналогично шаблонам C++?