Какова стратегия C для «подражания» шаблону C ++? - PullRequest
2 голосов
/ 13 июня 2010

После прочтения нескольких примеров по стеку и следуя некоторым ответам на мои предыдущие вопросы ( 1 ), я в конечном итоге пришел к «стратегии» для этого.

Я пришел к этому:

1) Есть раздел объявления в файле .h. Здесь я определю структуру данных и интерфейс доступа. Eg.:

/**
 * LIST DECLARATION. (DOUBLE LINKED LIST)
 */
#define NM_TEMPLATE_DECLARE_LIST(type) \
typedef struct nm_list_elem_##type##_s { \
    type data; \
    struct nm_list_elem_##type##_s *next; \
    struct nm_list_elem_##type##_s *prev; \
} nm_list_elem_##type ; \
typedef struct nm_list_##type##_s { \
    unsigned int size; \
    nm_list_elem_##type *head; \
    nm_list_elem_##type *tail; \
    int (*cmp)(const type e1, const type e2); \
} nm_list_##type ; \
\
nm_list_##type *nm_list_new_##type##_(int (*cmp)(const type e1, \
    const type e2)); \
\
(...other functions ...)

2) Обернуть функции в интерфейсе внутри MACROS:

/**
 * LIST INTERFACE
 */
#define nm_list(type) \
    nm_list_##type

#define nm_list_elem(type) \
    nm_list_elem_##type

#define nm_list_new(type,cmp) \
    nm_list_new_##type##_(cmp)

#define nm_list_delete(type, list, dst) \
    nm_list_delete_##type##_(list, dst)

#define nm_list_ins_next(type,list, elem, data) \
    nm_list_ins_next_##type##_(list, elem, data)

(...others...)

3) Реализация функций:

/**
 * LIST FUNCTION DEFINITIONS
 */
#define NM_TEMPLATE_DEFINE_LIST(type) \
nm_list_##type *nm_list_new_##type##_(int (*cmp)(const type e1, \
    const type e2)) \
{\
    nm_list_##type *list = NULL; \
    list = nm_alloc(sizeof(*list)); \
    list->size = 0; \
    list->head = NULL; \
    list->tail = NULL; \
    list->cmp = cmp; \
}\
void nm_list_delete_##type##_(nm_list_##type *list, \
    void (*destructor)(nm_list_elem_##type elem)) \
{ \
    type data; \
    while(nm_list_size(list)){ \
        data = nm_list_rem_##type(list, tail); \
        if(destructor){ \
            destructor(data); \
        } \
    } \
    nm_free(list); \
} \
(...others...)

Чтобы использовать эти конструкции, мне нужно создать два файла (назовем их templates.c и templates.h).

В templates.h мне нужно будет NM_TEMPLATE_DECLARE_LIST(int), NM_TEMPLATE_DECLARE_LIST(double), а в templates.c мне понадобится NM_TEMPLATE_DEFINE_LIST(int), NM_TEMPLATE_DEFINE_LIST(double), чтобы иметь код за списком целых, двойных и и так далее, сгенерировано.

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

Знаете ли вы другую стратегию для "подражания" (в какой-то момент) шаблонам в C ++? Знаете ли вы, как улучшить эту стратегию, чтобы сделать ее более децентрализованной, чтобы мне не понадобились два файла: templates.c и templates.h?

Ответы [ 3 ]

2 голосов
/ 13 июня 2010

Я, вероятно, не должен признаться в этом, но когда в прошлом мне понадобились «шаблонизированные» контейнеры на C, я написал «шаблонизированный класс очереди» в виде пары специальных файлов, например:

Файл MyQueue.include_h:

/** NOTE: THIS IS NOT a REAL .h FILE, it only looks like one!  Don't #include it! */
struct MyQueueClass
{
   void init_queue(MyQueueClass * q);
   void push_back(MyQueueClass * q, MyQueueClassItem * item);

   [....All the other standard queue header declarations would go here....]

   MyQueueClassItem * _head;
   MyQueueClassItem * _tail;
   int _size;
};

Файл MyQueue.include_c:

/** NOTE: THIS IS NOT A REAL .c FILE, it only looks like one! Don't compile directly! */
void init_queue(MyQueueClass * q)
{
   q->_size = 0;
   q->_head = q->_tail = NULL;
}

void push_back(MyQueueClass * q, MyQueueClassItem * item)
{
   if (q->_head == NULL) q->_head = q->_tail = item;
   else
   {
      q->_tail->_next = item;
      item->_prev = q->_tail;
      q->_tail = item;
   }
   q->_size++;
}

[....All the other standard queue function bodies would go here....]

Затем, всякий раз, когда я хотел "создать" шаблон моей очереди ", чтобы использовать определенный элемент,типа, я бы поместил что-то вроде этого в настоящий файл .c и .h:

В один из моих настоящих файлов .h:

#define MyQueueClass struct SomeSpecificQueueType
#define MyQueueClassItem struct SomeSpecificQueueTypeItem
# include "MyQueue.include_h"
#undef MyQueueClassItem
#undef MyQueueClass

В один из моих настоящих .cфайлы (не имеет значения, какой именно):

#define MyQueueClass struct SomeSpecificQueueType
#define MyQueueClassItem struct SomeSpecificQueueTypeItem
# include "MyQueue.include_c"
#undef MyQueueClass
#undef MyQueueClassItem

.... и до того препроцессор C действует как расширитель шаблонов для бедного человека, не требуя полного «определения шаблона»из гигантского заявления #define.

1 голос
/ 13 июня 2010

Ваш пример - только одно из многих возможных применений шаблонов - создание общей структуры данных.Этот пример не нуждается в выводе, который делает шаблоны мощными;просить что-то, что позволяет создавать общие структуры данных, на самом деле совсем не то же самое, что просить что-то эквивалентное шаблонам C ++.

Некоторые методы реализации, используемые для <tgmath.h>, могут дать некоторые возможности вывода типов, но они все еще намного слабее и менее переносимы, чем шаблоны C ++.

Для конкретного примера контейнеров я бы не стал беспокоиться - просто создайте список с данными void *, и либо используйте malloc и free для создания данных, либо дайте списку пару функцийуказатели для создания и уничтожения ценностей.Вы также можете просто положиться на клиента для управления данными, вместо того, чтобы иметь значение в качестве члена списка.Если вы хотите сохранить косвенность, используйте массив переменной длины в качестве члена данных.Поскольку C не так безопасен для типов, как C ++, наличие void * data не проблема.

Вы можете выполнить сложную генерацию кода с помощью макросов, но есть и другие инструменты для генерации кода.Лично мне нравится использовать XSLT для генерации кода, но тогда у вас есть полностью не-C-подобная часть вашего процесса сборки.

1 голос
/ 13 июня 2010

Избавьте себя от неприятностей и используйте существующий queue(3) набор макросов - проверенный и проверенный, используемый в исходных кодах ядра и т. Д.

...