我正在用 C 语言编写一个侵入式数据结构库。其中许多容器将共享功能。例如,将会有许多容器提供正向和反向迭代。因此,我创建了一个标头,将这些通用操作作为跨所有容器的宏提供。这只是前向迭代器。
#include "double_ended_priority_queue.h"
#include "ordered_map.h"
#include "realtime_ordered_map.h"
#define BEGIN(container_ptr) \
_Generic((container_ptr), \
double_ended_priority_queue *: depq_begin, \
ordered_map *: om_begin, \
realtime_ordered_map *: rom_begin)(container_ptr)
#define NEXT(container_ptr, iter_elem_ptr) \
_Generic((container_ptr), \
double_ended_priority_queue *: depq_next, \
ordered_map *: om_next, \
realtime_ordered_map *: rom_next)((container_ptr), \
(iter_elem_ptr))
用法如下所示。
struct val
{
int id;
int val;
ccc_depq_elem elem;
};
void
inorder_fill(int vals[], size_t size, double_ended_priority_queue *pq)
{
if (depq_size(pq) != size)
{
return;
}
size_t i = 0;
for (struct val *e = BEGIN(pq); e && i < size; e = NEXT(pq, &e->elem))
{
vals[i++] = e->val;
}
}
这看起来不错,并且这样的功能可以扩展到许多其他操作(emplace、Rust 的 Entry API、范围等)。但是,我必须将所有容器的标头包含在文件顶部的
_Generic
列表中。否则,当用户包含 generics.h
文件时,他们将收到编译错误,其中未包含的 _Generic
中的所有类型情况均包含未知类型。
随着更多共享这些功能的容器的实现,
generics.h
文件中包含的数量将大大增加。虽然 _Generic
的使用没有运行时开销,但我似乎用这种方法造成了巨大的编译时间膨胀。
_Generic
的良好用例?我想说代码非常好。
也许可以考虑将整个循环放在函数中以使代码更具可读性,而不是使用一些神秘的
BEGIN
/NEXT
宏。
如果函数已经就位且无法修改,则考虑包装函数(它将被内联/优化)。
也就是说,创建一个像
container_iterate()
这样的宏,并且该宏使用 _Generic
检查容器的类型,然后调用例如 depq_iterate
,它是一个包含 for 循环和对 depq_begin
/depq_next
的调用的包装函数
.
至于在添加更多类型时维护大量宏 - 总有 X 个宏,其中除其他外可用于生成
_Generic
关联列表,甚至是函数模板。它们以牺牲可读性为代价来提高可维护性。但这也许是一个单独问题的材料。