Насколько злой этот foreach C макрос? - PullRequest
3 голосов
/ 18 февраля 2011

Предисловие к этому вопросу заключается в том, что, как я понимаю, макросы C являются очень деликатным вопросом.Много раз они могут быть достигнуты не-макро-решением, которое является более безопасным и не подвержено классическим проблемам, таким как увеличенные аргументы;так что с этим из пути у меня есть реализация хеш-таблицы в C со связанными узлами для столкновения.Я уверен, что большинство из них видели это миллион раз, но это выглядит примерно так.

typedef struct tnode_t {
    char* key; void* value; struct tnode_t* next;
} tnode_t;

typedef struct table_t {
    tnode_t** nodes;
    unsigned long node_count;
    unsigned long iterator; // see macro below
        ...
}

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

#define tbleach(table, node) \
    for(node=table->nodes[table->iterator=0];\
        table->iterator<table->node_count;\
        node=node?node->next:table->nodes[++table->iterator])\
            if (node)

, который можно использовать так:

tnode_t* n;
tbleach(mytable, n) {
    do_stuff_to(n->key, n->value);
}

Единственный недостаток, который я вижу, - это то, что индекс итератора является частью таблицы, поэтому очевидно, что в одной и той же таблице нельзя одновременно выполнять два цикла.Я не уверен, как решить эту проблему, но я не рассматриваю это как прерыватель сделки, учитывая, насколько полезным будет этот маленький макрос.Итак, мой вопрос.

** updated **

Я включил предложение Зака ​​и Йенса, убрав проблему с «else» и объявив итератор внутри оператора for.Кажется, все работает, но Visual Studio жалуется, что «имя типа не разрешено», где используется макрос.Мне интересно, что именно здесь происходит, потому что он компилируется и запускается, но я не уверен, где находится итератор.

#define tbleach(table, node) \
    for(node=table->nodes[0], unsigned long i=0;\
        i<table->node_count;\
        node=node?node->next:table->nodes[++i])\
        if (!node) {} else

Является ли этот подход плохим тоном, и если нет, то есть ли способ улучшить его

Ответы [ 2 ]

7 голосов
/ 18 февраля 2011

Единственная действительно неприемлемая вещь - это то, что вы уже сказали - итератор является частью таблицы.Вы должны извлечь это следующим образом:

typedef unsigned long table_iterator_t;
#define tbleach(table, iter, node) \
    for ((iter) = 0, (node) = (table)->nodes[(iter)]; \
         (iter) < (table)->node_count; \
         (node) = ((node) && (node)->next) \
                  ? (node)->next : (table)->nodes[++(iter)])

// use:
table_iterator_t i;
tnode_t *n;
tbleach(mytable, i, n) {
    do_stuff_to(n->key, n->value);
}

Я также засосал оператор if в выражения цикла for, потому что это немного безопаснее (странные вещи не произойдут, если следующий токен после закрытияскобка тела петли else).Обратите внимание, что запись массива table->nodes[table->node_count] будет считываться из , в отличие от обычного соглашения, поэтому вам нужно выделить для нее место (и убедиться, что оно всегда NULL).Я думаю, это также относится и к вашей версии.

РЕДАКТИРОВАТЬ: Исправлена ​​логика для случая, когда запись в таблице NULL.

1 голос
/ 19 февраля 2011

В дополнение к ответу Зака ​​об итераторе и о завершающем if, который может скрыть синтаксис:

Если у вас есть C99, используйте локальные переменные в цикле for. Это позволит избежать неприятных сюрпризов переменных окружения, которые будут содержать висячие указатели. Используйте что-то вроде этого внутри макроса:

for(nodeS node = ...

И, имена с _t в конце зарезервированы POSIX. Так что лучше не используйте их для своих собственных типов.

...