Я реализую набор общих, но не столь тривиальных (или подверженных ошибкам) структур данных для C (здесь) и только что пришел с идеей, которая заставила меня задуматься.
Короче говоря, что является лучшим способом реализовать две структуры, которые используют похожие алгоритмы, но имеют разные интерфейсы, не имея необходимости копировать/вставлять/переписывать алгоритм?. В лучшем случае, я имею в виду большинство поддерживаемых и отлаживаемых.
Я думаю, что очевидно, почему вы не хотели бы иметь две копии одного и того же алгоритма.
Мотивация
Скажите, что у вас есть структура (назовите ее map
) с набором связанных функций (map_*()
). Так как карта должна отображать что угодно, мы обычно реализуем ее с помощью void *key
и void *data
. Однако подумайте о карте от int
до int
. В этом случае вам нужно будет хранить все ключи и данные в другом массиве и указывать их адреса на map
, что не так удобно.
Теперь представьте, была ли подобная структура (назовите ее mapc
, c для "копий" ), которая во время инициализации принимает sizeof(your_key_type)
и sizeof(your_data_type)
и задана void *key
и void *data
при вставке, она будет использовать memcpy
, чтобы скопировать ключи и данные на карте, а не просто удерживать указатели. Пример использования:
int i;
mapc m;
mapc_init(&m, sizeof(int), sizeof(int));
for (i = 0; i < n; ++i)
{
int j = rand(); /* whatever */
mapc_insert(&m, &i, &j);
}
что довольно приятно, потому что мне не нужно сохранять еще один массив из i
и j
s.
Мои идеи
В приведенном выше примере map
и mapc
очень тесно связаны. Если вы думаете об этом, структуры и функции map
и set
также очень похожи. Я подумал о следующих способах реализации их алгоритма только один раз и использовать его для всех из них. Однако ни один из них не удовлетворяет меня.
-
Использовать макросы. Напишите код функции в файле заголовка, оставив зависимые от структуры элементы как макросы. Для каждой структуры определите правильные макросы и включите файл:
map_generic.h #define INSERT(x) x##_insert int INSERT(NAME)(NAME *m, PARAMS) { // create node ASSIGN_KEY_AND_DATA(node) // get m->root // add to tree starting from root // rebalance from node to root // etc } map.c #define NAME map #define PARAMS void *key, void *data #define ASSIGN_KEY_AND_DATA(node) \ do {\ node->key = key;\ node->data = data;\ } while (0) #include "map_generic.h" mapc.c #define NAME mapc #define PARAMS void *key, void *data #define ASSIGN_KEY_AND_DATA(node) \ do {\ memcpy(node->key, key, m->key_size);\ memcpy(node->data, data, m->data_size);\ } while (0) #include "map_generic.h"
Этот метод не слишком плох, но он не настолько изящный.
-
Используйте указатели на функции. Для каждой части, которая зависит от структуры, передайте указатель на функцию.
map_generic.c int map_generic_insert(void *m, void *key, void *data, void (*assign_key_and_data)(void *, void *, void *, void *), void (*get_root)(void *)) { // create node assign_key_and_data(m, node, key, data); root = get_root(m); // add to tree starting from root // rebalance from node to root // etc } map.c static void assign_key_and_data(void *m, void *node, void *key, void *data) { map_node *n = node; n->key = key; n->data = data; } static map_node *get_root(void *m) { return ((map *)m)->root; } int map_insert(map *m, void *key, void *data) { map_generic_insert(m, key, data, assign_key_and_data, get_root); } mapc.c static void assign_key_and_data(void *m, void *node, void *key, void *data) { map_node *n = node; map_c *mc = m; memcpy(n->key, key, mc->key_size); memcpy(n->data, data, mc->data_size); } static map_node *get_root(void *m) { return ((mapc *)m)->root; } int mapc_insert(mapc *m, void *key, void *data) { map_generic_insert(m, key, data, assign_key_and_data, get_root); }
Этот метод требует записи большего количества функций, которые можно было бы избежать в методе макросов (как вы можете видеть, код здесь длиннее) и не позволяет оптимизаторам встроить функции (поскольку они не видны для
map_generic.c
файл).
Итак, как бы вы начали реализовывать что-то вроде этого?
Примечание. Я написал код в форме вопроса о переполнении стека, поэтому извините меня, если есть незначительные ошибки.
Боковой вопрос: у кого-то есть лучшая идея для суффикса, который говорит, что "эта структура копирует данные вместо указателя"? Я использую c
, который говорит "копии", но на английском может быть гораздо лучшее слово, о котором я не знаю.
Обновление:
У меня появилось третье решение. В этом решении записывается только одна версия map
, которая хранит копию данных (mapc
). Эта версия использовала бы memcpy
для копирования данных. Другой map
является интерфейсом к этому, принимая указатели void *key
и void *data
и отправляя &key
и &data
в mapc
так, чтобы адрес, который они содержат, был скопирован (используя memcpy
).
Это решение имеет недостаток, что нормальное назначение указателя выполняется memcpy
, но оно полностью решает проблему в противном случае и очень чистое.
В качестве альтернативы можно реализовать только map
и использовать дополнительный vectorc
с mapc
, который сначала копирует данные в вектор, а затем передает адрес map
. Это имеет побочный эффект, что удаление из mapc
будет либо значительно медленнее, либо оставить мусор (или потребовать от других структур повторного использования мусора).
Обновление 2:
Я пришел к выводу, что неосторожные пользователи могут использовать мою библиотеку так, как они пишут С++, копировать после копирования после копирования. Поэтому я отказываюсь от этой идеи и принимаю только указатели.