实现不同但相似的结构/function集而无需复制粘贴

我正在为C( 这里 )实现一组常见但又不那么简单(或容易出错)的数据结构,并且只是提出了让我思考的想法。

简而言之,问题是,实现两个使用类似算法但具有不同接口的结构的最佳方法是什么,而无需复制粘贴/重写算法? 最好的,我的意思是大多数可维护和可调试。

我认为很明显为什么你不想要同一算法的两个副本。

动机

假设你有一组结构(称之为map ),带有一组相关的函数( map_*() )。 由于地图需要将任何内容映射到任何东西,我们通常会使用void *keyvoid *data来实现它。 但是,想一下intint的映射。 在这种情况下,您需要将所有键和数据存储在另一个数组中,并将其地址提供给map ,这不太方便。

现在假设有一个类似的结构(称之为mapc ,c表示“副本”),在初始化期间需要sizeof(your_key_type)sizeof(your_data_type)并在insert上给出void *keyvoid *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 s和j s数组。

我的想法

在上面的示例中, mapmapc非常密切相关。 如果你考虑一下, mapset结构和function也非常相似。 我已经想到了以下几种方法来实现它们的算法只用一次并将它用于所有这些算法。 然而,他们都不是很满意我。

  1. 使用宏。 将函数代码写入头文件,将结构相关的东西保留为宏。 对于每个结构,定义适当的宏并包含文件:

     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" 

    这种方法不是一半坏,但它不是那么优雅。

  2. 使用函数指针。 对于依赖于结构的每个部分,传递一个函数指针。

     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 ,即保留数据副本的mapmapc )。 此版本将使用memcpy复制数据。 另一个map是这个接口,接受void *keyvoid *data指针,并将&key&data发送到mapc以便复制它们包含的地址(使用memcpy )。

这个解决方案的缺点是,正常的指针分配由memcpy完成,但它完全解决了问题,并且非常干净。

或者,人们只能实现map并使用带mapc的额外vectorcmapc首先将数据复制到矢量,然后将地址提供给map 。 这具有副作用,即从mapc中删除要么明显更慢,要么留下垃圾(或者需要其他结构来重用垃圾)。


更新2:

我得出结论,粗心的用户可能会像编写C ++一样使用我的库,复制后复制后复制。 因此,我放弃了这个想法,只接受指针。

您还没有考虑过第三个选项:您可以创建外部脚本(用其他语言编写)以从一系列模板生成代码。 这与宏方法类似,但您可以使用Perl或Python等语言生成代码。 由于这些语言比C预处理器更强大,因此可以避免通过宏执行模板时固有的一些潜在问题。 我曾经尝试过使用这种方法,我很想在你的例子中使用复杂的宏#1。 最后,事实certificate它比使用C预处理器更不容易出错。 缺点是在编写生成器脚本和更新makefile之间,最初设置起来有点困难(但最终IMO值得)。

您大致涵盖了两种可能的解决方

预处理器宏大致对应于C ++模板,具有相同的优点和缺点:

  • 他们很难读。
  • 复杂的宏通常很难使用(考虑参数的类型安全性等)
  • 它们只是更多代码的“生成器”,因此在编译输出中仍然存在许多重复性。
  • 另一方面,它们允许编译器优化很多东西。

函数指针大致对应于C ++多态,它们是IMHO更清晰且通常更易于使用的解决方案,但它们在运行时带来了一些成本(对于紧密循环,很少额外的函数调用可能很昂贵)。

我通常更喜欢函数调用,除非性能非常关键。

您正在寻找的是多态性。 C ++,C#或其他面向对象的语言更适合这项任务。 虽然很多人试图在C中实现多态行为。

Code Project有一些关于这个主题的好文章/教程:

http://www.codeproject.com/Articles/10900/Polymorphism-in-C

http://www.codeproject.com/Articles/108830/Inheritance-and-Polymorphism-in-C