通用性与类型安全性? 在C中使用void *

来自OO(C#,Java,Scala),我非常重视代码重用和类型安全的原则。 上述语言中的类型参数可以完成这项工作并启用通用数据结构,这些结构既是类型安全的,也不会“浪费”代码。

当我陷入C时,我意识到我必须做出妥协,我希望它是正确的。 我的数据结构在每个节点/元素中都有一个void * ,我失去了类型安全性,或者我必须为我想要使用它们的每种类型重新编写我的结构和代码。

代码的复杂性是一个显而易见的因素:遍历数组或链表是微不足道的,并且在结构*next添加*next不是额外的努力; 在这些情况下,不尝试重用结构和代码是有意义的。 但对于更复杂的结构,答案并不那么明显。

还有模块化和可测试性:将类型及其操作与使用该结构的代码分离,使测试更容易。 反之亦然:在一个结构上测试一些代码的迭代,而它试图做其他事情变得混乱。

那么你的建议是什么? void *和重用或类型安全和重复的代码? 有没有一般原则? 当我不适合时,我是否试图强迫OO进行程序化?

编辑 :请不要推荐C ++,我的问题是关于C!

我会说使用void *所以你可以重用代码。 重新实现例如链表更多的工作,而不是确保正确地获取/设置列表中的数据。

尽可能多地从glib中获取提示,我发现它们的数据结构非常好用且易于使用,并且由于类型安全性的损失而没有遇到什么麻烦。

我认为你必须在两者之间取得平衡,正如你的建议。 如果代码只有几行而且微不足道,我会复制它,但如果它更复杂,我会考虑使用void*来避免在几个地方进行任何潜在的bug修复和维护,并减少代码大小。

如果你看一下C运行时库,有几个“generics”函数可以使用void* ,一个常见的例子就是使用qsort排序。 为您要排序的每种类型复制此代码会很疯狂。

使用void指针没有错。 因为转换是在内部完成的,所以在将它们分配给指针类型的变量时甚至不必抛出它们。 它值得一看: http ://www.cpax.org.uk/prg/writings/casting.php

这个问题的答案与在C ++中获取链接列表的高效模板相同。

a)创建使用void *或某些Abstracted Type的算法的抽象版本

b)创建一个轻量级公共接口来调用Abstracted Type算法并在它们之间进行关联。

例如。

 typedef struct simple_list { struct simple_list* next; } SimpleList; void add_to_list( SimpleList* listTop, SimpleList* element ); SimpleList* get_from_top( SimpleList* listTop ); // the rest #define ListType(x) \ void add_ ## x ( x* l, x* e ) \ { add_to_list( (SimpleList*)l, (SimpleList*)x ); } \ void get_ ## x ( x* l, x* e ) \ { return (x*) get_from_to( (SimpleList*)l ); } \ /* the rest */ typedef struct my_struct { struct my_struct* next; /* rest of my stuff */ } MyStruct; ListType(MyStruct) MyStruct a; MyStruct b; add_MyStruct( &a, &b ); MyStruct* c = get_MyStruct(&a); 

等等

我们在C中大量使用OO,但仅用于封装和抽象,没有多态性等。

这意味着我们有特定的类型,比如FooBar(Foo a,…)但是,对于我们的集合“类”,我们使用void *。 只需使用void *,可以使用多种类型,但是,通过这样做,确保您不需要参数属于特定类型。 根据集合,拥有void *是正常的,因为集合并不关心类型。 但是,如果你的函数可以接受类型a和类型b而不是其他类型,那么请创建两个变体,一个用于a,一个用于b。

重点是只在不关心类型时才使用void *。

现在,如果你有50个具有相同基本结构的类型(比方说,int a; int b;作为所有类型的第一个成员),并希望函数对这些类型起作用,只需使公共第一个成员自己成为一个类型,然后让函数接受这个,并传递object-> ab或(AB *)对象是你的类型是不透明的,如果ab是你的struct中的第一个字段,两者都会起作用。

您可以使用宏,它们可以使用任何类型,编译器将静态检查扩展代码。 缺点是代码密度(二进制代码)会恶化,并且调试起来更加困难。

我前段时间问过这个关于generics函数的问题 ,答案可以帮到你。

您可以有效地向C数据结构添加类型信息,inheritance和多态,这就是C ++的function。 ( http://www.embedded.com/97/fe29712.htm

绝对通用的void* ,永远不会重复代码!

考虑到许多C程序员和许多主要的C项目都考虑过这种困境。 我遇到的所有严肃的C项目,无论是开源还是商业,都选择了通用的void* 。 当仔细使用并包装到一个好的API中时,它几乎不会成为库用户的负担。 此外, void*是惯用的C,直接推荐在K&R2中。 这是人们期望编写代码的方式,而其他任何东西都会令人惊讶并被严重接受。

您可以使用C构建(某种)OO框架,但是您错过了很多好处……就像编译器理解的OO类型系统一样。 如果你坚持用类C语言做OO,那么C ++是更好的选择。 它比香草C更复杂,但至少你得到适当的语言支持OO。

编辑:好的…如果你坚持我们不推荐使用C ++,我建议你不要在C中做OO。快乐吗? 就你的OO习惯而言,你应该考虑 “对象”,但是将inheritance和多态从你的实现策略中删除。 应谨慎使用Genericity(使用函数指针)。

编辑2:实际上,我认为在通用C列表中使用void *是合理的。 它只是试图使用宏,函数指针,调度和我认为这是一个坏主意的那种废话来构建一个模拟OO框架。

在Java中, java.util包中的所有集合实际上都等效于void* pointer( Object )。

是的,generics(在1.5中引入)添加语法糖并阻止您编写不安全的赋值,但是存储类型仍然是Object

所以,我认为当你使用void*作为通用框架类型时,没有提交OO犯罪。

如果您经常在代码中执行此操作,我还将添加特定于类型的内联或宏包装器,以从通用结构中分配/检索数据。

PS你不应该做的一件事是使用void**来返回已分配/重新分配的generics类型。 如果检查malloc/realloc的签名,您将看到可以在没有可怕的void**指针的情况下实现正确的内存分配。 我只是在说这个,因为我在一些开源项目中看到过这个,我不想在这里说出来。

通用容器可以包含一些工作,以便可以在类型安全的版本中进行实例化。 这是一个示例,下面链接的完整标题:

/ *通用实现* /

 struct deque *deque_next(struct deque *dq); void *deque_value(const struct deque *dq); /* Prepend a node carrying `value` to the deque `dq` which may * be NULL, in which case a new deque is created. * O(1) */ void deque_prepend(struct deque **dq, void *value); 

从可用于实例化特定包装类型的双端队列的标头

 #include "deque.h" #ifndef DEQUE_TAG #error "Must define DEQUE_TAG to use this header file" #ifndef DEQUE_VALUE_TYPE #error "Must define DEQUE_VALUE_TYPE to use this header file" #endif #else #define DEQUE_GEN_PASTE_(x,y) x ## y #define DEQUE_GEN_PASTE(x,y) DEQUE_GEN_PASTE_(x,y) #define DQTAG(suffix) DEQUE_GEN_PASTE(DEQUE_TAG,suffix) #define DQVALUE DEQUE_VALUE_TYPE #define DQREF DQTAG(_ref_t) typedef struct { deque_t *dq; } DQREF; static inline DQREF DQTAG(_next) (DQREF ref) { return (DQREF){deque_next(ref.dq)}; } static inline DQVALUE DQTAG(_value) (DQREF ref) { return deque_value(ref.dq); } static inline void DQTAG(_prepend) (DQREF *ref, DQVALUE val) { deque_prepend(&ref->dq, val); } 
  • deque.h: http ://ideone.com/eDNBN
  • deque_gen.h: http ://ideone.com/IkJRq