具有宏的类型安全的通用容器

我正在尝试使用宏在C中创建一个类型安全的通用链表。 它应该与模板在C ++中的工作方式类似。 例如,

LIST(int) *list = LIST_CREATE(int); 

我的第一次尝试是#define LIST(TYPE) (我上面使用的宏)来定义struct _List_##TYPE {...} 。 但是,这不起作用,因为每次我声明一个新列表时都会重新定义结构。 我通过这样做解决了这个问题:

 /* You would first have to use this macro, which will define the `struct _List_##TYPE`... */ DEFINE_LIST(int); int main(void) { /* ... And this macro would just be an alias for the struct, it wouldn't actually define it. */ LIST(int) *list = LIST_CREATE(int); return 0; } /* This is how the macros look like */ #define DEFINE_LIST(TYPE) \ struct _List_##TYPE \ { \ ... \ } #define LIST(TYPE) \ struct _List_##TYPE 

但另一个问题是,当我有多个文件使用DEFINE_LIST(int) ,例如,其中一些文件相互包含,那么仍然会有相同结构的多个定义。 有没有办法让DEFINE_LIST检查是否已经定义了结构?

 /* one.h */ DEFINE_LIST(int); /* two.h */ #include "one.h" DEFINE_LIST(int); /* Error: already defined in one.h */ 

在C ++获取模板之前我在C中解决了这个问题,我仍然有代码。

您不能以完全局限于头文件的方式定义具有宏的真正通用的类型安全容器-T模板。 标准预处理器不提供“推送”和“弹出”您需要的宏分配的方法,以便通过嵌套和连续的扩展上下文保持其完整性。 一旦你尝试通过定义一个容器容器T来吃自己的狗粮,你就会遇到嵌套的上下文。

正如我们所看到的,事情可以做到,但正如@immortal所暗示的,它需要为你需要的每个T值生成不同的.h.c文件。 例如,您可以list_type.inl联文件中定义一个完全通用的带有宏的list-of-T,例如list_type.inl ,然后在每对小型设置包装器中包含list_type.inllist_float.hlist_float.c – 分别定义和实现float-list容器。 类似地,对于list-of-int,list-of-list-of-float,list-of-of-list-of-double,等等。

一个示意图将使所有清楚。 但首先要全面了解自己吃狗食的挑战。

将这样的二阶容器视为一个列表的列表。 我们希望能够通过为我们的宏列表T解决方案设置T = list-of-thingummy来实例化这些。 但绝不是list-of-thingummy将成为POD数据类型。 无论list-of-thingummy是我们自己的dogfood还是别人的,它都将是一个生活在堆上的抽象数据类型,并通过一个typedef-ed指针类型表示给它的用户。 或者至少,它将在堆上保留动态组件。 无论如何,不​​是POD。

这意味着我们的T列表解决方案仅仅被告知T = list-of-thingummy是不够的。 还必须告诉它是否需要非POD复制构造和破坏,如果是,则如何复制 – 构造和销毁它。 用C表示,这意味着:

  • 复制构造:给定此区域的地址,如何在未提交的内存的T大小区域中创建给定T的副本。

  • 毁灭:如何破坏给定地址的T.

我们可以不知道非T参数的默认构造或构造,因为我们可以合理地限制我们的T列表解决方案来控制从用户提供的原件复制的对象。 但我们必须复制它们,我们必须处理我们的副本。

接下来,假设除了list-of-T之外,我们还希望为T-set或T-map-to-T2提供模板。 这些按键排序的数据类型添加了另一个参数,我们必须为T或T1的任何非POD值插入,即如何订购密钥类型的任何两个对象。 实际上,对于memcmp()不会执行的任何键数据类型,我们都需要该参数。

注意到,我们将坚持原理图示例中更简单的T列表问题; 为了进一步简化,我将忘记任何const API的可取性。

对于这个和任何其他模板容器类型,我们需要一些令牌粘贴宏,让我们可以方便地组合函数和类型的标识符,以及可能的其他实用程序宏。 这些都可以放在标题中,比如说macro_kit.h ,例如:

 #ifndef MACRO_KIT_H #define MACRO_KIT_H /* macro_kit.h */ #define _CAT2(x,y) x##y // Concatenate 2 tokens x and y #define CAT2(x,y) _CAT2(x,y) // Concatenate 3 tokens x, y and z #define CAT3(x,y,z) CAT2(x,CAT2(y,z)) // Join 2 tokens x and y with '_' = x_y #define JOIN2(x,y) CAT3(x,_,y) // Join 3 tokens x, y and z with '_' = x_y_z #define JOIN3(x,y,z) JOIN2(x,JOIN2(y,z)) // Compute the memory footprint of n T's #define SPAN(n,T) ((n) * sizeof(T)) #endif 

现在到list_type.inl的原理图结构:

 //! There is intentionally no idempotence guard on this file #include "macro_kit.h" #include  #ifndef INCLUDE_LIST_TYPE_INL #error This file should only be included from headers \ that define INCLUDE_LIST_TYPE_INL #endif #ifndef LIST_ELEMENT_TYPE #error Need a definition for LIST_ELEMENT_TYPE #endif /* list_type.inl Defines and implements a generic list-of-T container for T the current values of the macros: - LIST_ELEMENT_TYPE: - must have a definition = the datatype (or typedef alias) for \ which a list container is required. - LIST_ELEMENT_COPY_INITOR: - If undefined, then LIST_ELEMENT_TYPE is assumed to be copy- initializable by the assignment operator. Otherwise must be defined as the name of a copy initialization function having a prototype of the form: LIST_ELEMENT_TYPE * copy_initor_name(LIST_ELEMENT_TYPE *pdest, LIST_ELEMENT_TYPE *psrc); that will attempt to copy the LIST_ELEMENT_TYPE at `psrc` into the uncommitted memory at `pdest`, returning `pdest` on success and NULL on failure. NB This file itself defines the copy initializor for the list-type that it generates. - LIST_ELEMENT_DISPOSE If undefined, then LIST_ELEMENT_TYPE is assumed to need no destruction. Otherwise the name of a destructor function having a protoype of the form: void dtor_name(LIST_ELEMENT_TYPE pt*); that appropriately destroys the LIST_ELEMENT_TYPE at `pt`. NB This file itself defines the destructor for the list-type that it generates. */ /* Define the names of the list-type to generate, eg list_int, list_float */ #define LIST_TYPE JOIN2(list,LIST_ELEMENT_TYPE) /* Define the function-names of the LIST_TYPE API. Each of the API macros LIST_XXXX generates a function name in which LIST becomes the value of LIST_TYPE and XXXX becomes lowercase, eg list_int_new */ #define LIST_NEW JOIN2(LIST_TYPE,new) #define LIST_NODE JOIN2(LIST_TYPE,node) #define LIST_DISPOSE JOIN2(LIST_TYPE,dispose) #define LIST_COPY_INIT JOIN2(LIST_TYPE,copy_init) #define LIST_COPY JOIN2(LIST_TYPE,copy) #define LIST_BEGIN JOIN2(LIST_TYPE,begin) #define LIST_END JOIN2(LIST_TYPE,end) #define LIST_SIZE JOIN2(LIST_TYPE,size) #define LIST_INSERT_BEFORE JOIN3(LIST_TYPE,insert,before) #define LIST_DELETE_BEFORE JOIN3(LIST_TYPE,delete,before) #define LIST_PUSH_BACK JOIN3(LIST_TYPE,push,back) #define LIST_PUSH_FRONT JOIN3(LIST_TYPE,push,front) #define LIST_POP_BACK JOIN3(LIST_TYPE,pop,back) #define LIST_POP_FRONT JOIN3(LIST_TYPE,pop,front) #define LIST_NODE_GET JOIN2(LIST_NODE,get) #define LIST_NODE_NEXT JOIN2(LIST_NODE,next) #define LIST_NODE_PREV JOIN2(LIST_NODE,prev) /* Define the name of the structure used to implement a LIST_TYPE. This structure is not exposed to user code. */ #define LIST_STRUCT JOIN2(LIST_TYPE,struct) /* Define the name of the structure used to implement a node of a LIST_TYPE. This structure is not exposed to user code. */ #define LIST_NODE_STRUCT JOIN2(LIST_NODE,struct) /* The LIST_TYPE API... */ // Define the abstract list type typedef struct LIST_STRUCT * LIST_TYPE; // Define the abstract list node type typedef struct LIST_NODE_STRUCT * LIST_NODE; /* Return a pointer to the LIST_ELEMENT_TYPE in a LIST_NODE `node`, or NULL if `node` is null */ extern LIST_ELEMENT_TYPE * LIST_NODE_GET(LIST_NODE node); /* Return the LIST_NODE successor of a LIST_NODE `node`, or NULL if `node` is null. */ extern LIST_NODE LIST_NODE_NEXT(LIST_NODE node); /* Return the LIST_NODE predecessor of a LIST_NODE `node`, or NULL if `node` is null. */ extern LIST_NODE LIST_NODE_PREV(LIST_NODE node); /* Create a new LIST_TYPE optionally initialized with elements copied from `start` and until `end`. If `end` is null it is assumed == `start` + 1. If `start` is not NULL then elements will be appended to the LIST_TYPE until `end` or until an element cannot be successfully copied. The size of the LIST_TYPE will be the number of successfully copied elements. */ extern LIST_TYPE LIST_NEW(LIST_ELEMENT_TYPE *start, LIST_ELEMENT_TYPE *end); /* Dispose of a LIST_TYPE If the pointer to LIST_TYPE `plist` is not null and addresses a non-null LIST_TYPE then the LIST_TYPE it addresses is destroyed and set NULL. */ extern void LIST_DISPOSE(LIST_TYPE * plist); /* Copy the LIST_TYPE at `psrc` into the LIST_TYPE-sized region at `pdest`, returning `pdest` on success, else NULL. If copying is unsuccessful the LIST_TYPE-sized region at `pdest is unchanged. */ extern LIST_TYPE * LIST_COPY_INIT(LIST_TYPE *pdest, LIST_TYPE *psrc); /* Return a copy of the LIST_TYPE `src`, or NULL if `src` cannot be successfully copied. */ extern LIST_TYPE LIST_COPY(LIST_TYPE src); /* Return a LIST_NODE referring to the start of the LIST_TYPE `list`, or NULL if `list` is null. */ extern LIST_NODE LIST_BEGIN(LIST_TYPE list); /* Return a LIST_NODE referring to the end of the LIST_TYPE `list`, or NULL if `list` is null. */ extern LIST_NODE LIST_END(LIST_TYPE list); /* Return the number of LIST_ELEMENT_TYPEs in the LIST_TYPE `list` or 0 if `list` is null. */ extern size_t LIST_SIZE(LIST_TYPE list); /* Etc. etc. - extern prototypes for all API functions. ... ... */ /* If LIST_IMPLEMENT is defined then the implementation of LIST_TYPE is compiled, otherwise skipped. #define LIST_IMPLEMENT to include this file in the .c file that implements LIST_TYPE. Leave it undefined to include this file in the .h file that defines the LIST_TYPE API. */ #ifdef LIST_IMPLEMENT // Implementation code now included. // Standard library #includes...? // The heap structure of a list node struct LIST_NODE_STRUCT { struct LIST_NODE_STRUCT * _next; struct LIST_NODE_STRUCT * _prev; LIST_ELEMENT_TYPE _data[1]; }; // The heap structure of a LIST_TYPE struct LIST_STRUCT { size_t _size; struct LIST_NODE_STRUCT * _anchor; }; /* Etc. etc. - implementations for all API functions ... ... */ /* Undefine LIST_IMPLEMENT whenever it was defined. Should never fall through. */ #undef LIST_IMPLEMENT #endif // LIST_IMPLEMENT /* Always undefine all the LIST_TYPE parameters. Should never fall through. */ #undef LIST_ELEMENT_TYPE #undef LIST_ELEMENT_COPY_INITOR #undef LIST_ELEMENT_DISPOSE /* Also undefine the "I really meant to include this" flag. */ #undef INCLUDE_LIST_TYPE_INL 

请注意, list_type.inl没有针对list_type.inl包含的宏保护。 您希望每次看到它时至少包含其中的一部分 – 至少是模板API。

如果您阅读文件顶部的注释,您可以猜测如何编写包装头以导入list-of-int容器类型。

 #ifndef LIST_INT_H #define LIST_INT_H /* list_int.h*/ #define LIST_ELEMENT_TYPE int #define INCLUDE_LIST_TYPE_INL #include "list_type.inl" #endif 

以及如何编码包装头以导入list-of-list-of-int容器类型:

 #ifndef LIST_LIST_INT_H #define LIST_LIST_INT_H /* list_list_int.h*/ #define LIST_ELEMENT_TYPE list_int #define LIST_ELEMENT_COPY_INIT list_int_copy_init #define LIST_ELEMENT_DISPOSE list_int_dispose #define INCLUDE_LIST_TYPE_INL #include "list_type.inl" #endif 

您的应用程序可以安全地包含此类包装器,例如

 #include "list_int.h" #include "list_list_int.h" 

尽管事实上他们以冲突的方式定义了LIST_ELEMENT_TYPE ,因为list_type.inl总是#undefs在完成列表类型时参数化列表类型的所有宏:查看文件的最后几行。

LIST_IMPLEMENT请注意宏LIST_IMPLEMENT的使用。 如果在解析list_type.inl时未定义,则仅公开模板API; 跳过模板实现。 如果定义了LIST_IMPLEMENT则编译整个文件。 因此,通过不定义LIST_IMPLEMENT ,我们的包装头只导入列表类型的API。

相反,对于我们的包装源文件list_int.clist_list_int.c ,我们将定义LIST_IMPLEMENT 。 在那之后,除了包含相应的标题之外没什么可做的:

 /* list_int.c */ #define LIST_IMPLEMENT #include "list_int.h" 

和:

 /* list_list_int.c*/ #include "list_int.h" #define LIST_IMPLEMENT #include "list_list_int.h" 

现在,在您的应用程序中,不会出现列表模板宏。 您的包装头解析为“真实代码”:

 #include "list_int.h" #include "list_list_int.h" // etc. int main(void) { int idata[10] = {1,2,3,4,5,6,7,8,9,10}; //... list_int lint = list_int_new(idata,idata + 10); //... list_list_int llint = list_list_int_new(&lint,0); //... list_int_dispose(&lint); //... list_list_int_dispose(&llint); //... exit(0); } 

通过这种方式为自己配备“C模板库”,唯一(!)的工作就是为每个容器类型编写.inl文件,并对其进行非常彻底的测试。 然后,您可能会为本机数据类型和容器类型的每种组合生成一个目标文件和标头,用于现成的链接,并根据需要快速删除其他类型的.h.c包装器。

毋庸置疑,只要C ++发布了模板,我就会以这种方式让他们大汗淋漓。 但是,如果出于某种原因,C是唯一的选择,它可以通过这种方式完成。

您总是可以向DEFINE_LIST宏添加第二个参数,以允许您“命名”列表。 例如:

 #define DEFINE_LIST(TYPE, NAME) \ struct _List_##TYPE_##NAME \ { \ TYPE member_1; \ struct _List_##TYPE_##NAME* next; \ } 

然后你可以简单地做:

 DEFINE_LIST(int, my_list); //... more code that uses the "my_list" type 

当两个不同的头文件相互包含时,你只需要限制自己不重复使用相同的列表“name”,并且都使用DEFINE_LIST宏。 使用LIST_CREATE等时,您还必须按名称引用列表。

将列表传递给您编写的函数时,您始终可以创建用户定义的“命名”版本转换为“通用”类型。 这不应该影响任何东西,因为结构中的实际信息保持不变,而“name”标签只是将类型与声明而不是二进制立场区分开来。 例如,这是一个获取存储int类型的列表对象的函数:

 #define GENERIC_LIST_PTR(TYPE) struct _generic_list_type_##TYPE* #define LIST_CAST_PTR(OBJ, TYPE) (GENERIC_LIST_PTR(TYPE))(OBJ) void function(GENERIC_LIST_PTR(INT) list) { //...use list as normal (ie, access it's int data-member, etc.) } DEFINE_LIST(int, my_list); int main() { LIST(int, my_list)* list = LIST_CREATE(int, my_list); function(LIST_CAST_PTR(list, int)); //...more code return 0; } 

我知道这不一定是最方便的事情,但这确实解决了命名问题,并且您可以控制在其他用户不会添加的某些私有头文件中创建struct _generic_list_type_XXX哪个版本(除非他们希望-do对于他们自己的类型)…但它将是一种机制,用于将通用列表类型的声明和定义与实际的用户定义列表类型分开。

你为什么不用图书馆? 我喜欢使用GLib,但我讨厌代码中的void指针,为了获得GLib提供的数据类型的类型安全版本,我编写了一些非常简单的宏:

http://pastebin.com/Pc0KsadV

如果我想要一个Symbol *列表(假设它是我之前定义的类型),我只需要:

 GLIST_INSTANCE(SymbolList, Symbol*, symbol_list); 

如果您不想为简单的链表使用整个库(这可能是一种过度杀伤),请实现一个处理void *的列表,并创建一些函数来封装并进行正确的类型转换。

如何创建list_template.h文件,然后为模板的每个实例创建list_TYPE.h文件和list_TYPE.c文件。 当然,这些可以带有合适的头保护器。 然后,您只能包含模板标题,但请确保将所有.c文件添加到编译和链接过程中,它应该可以正常工作。

这基本上是C ++自动为您做的…复制实例…

我真的怀疑你可以在一个宏中检查存在和定义(一个结构)。 在DEFINE_LIST(int)之前DEFINE_LIST(int)另一个#ifndef检查。 它不优雅,但做你想要的。

可以使用宏创建通用和类型安全的容器。 从计算理论的角度来看,宏扩展生成的语言(代码)可以通过非确定性下推自动机来识别,这意味着它至多是一个无上下文的语法。 前面提到的语句使得我们的目标似乎无法实现,因为容器及其附属迭代器应该记住它们包含的类型,但这只能通过上下文敏感的语法来完成。 但是,我们可以做一些技巧!

成功的关键在于编译过程,构建符号表。 如果在编译器查询表并且没有发生不安全类型转换时可以识别变量的类型,那么它被认为是类型安全的。 因此,我们必须为每个struct赋予一个特殊名称,因为如果在同一范围的范围内声明了两个或更多结构,则结构名称可能会发生冲突。 最简单的方法是将当前行号附加到结构名称。 标准C支持自ANSI C(C89 / C90)以来的预定义宏__LINE__和宏级联 / 字符串化 。

然后,我们要做的是将一些属性隐藏到我们上面定义的结构中。 如果要在运行时创建另一个列表记录,在结构中放置一个指向它自己的指针将实际解决问题。 但是,这还不够。 我们可能需要一个额外的变量来存储我们在运行时分配的列表记录数。 这有助于我们弄清楚当程序员明确销毁列表时如何释放内存。 此外,我们可以利用__typeof__()扩展,这在宏编程中被广泛使用。

我是OpenGC3的作者,旨在使用宏构建类型安全的通用容器,这是一个简短的示例,说明了这个库的工作原理:

 ccxll(int) list; // declare a list of type int ccxll_init(list); // initialize the list record for (int cnt = 8; cnt-- > 0; ) // ccxll_push_back(list, rand()); // insert "rand()" to the end ccxll_sort(list); // sort with comparator: XLEQ CCXLL_INCR_AUTO(pnum, list) // traverse the list forward: printf("num = %d\n", *pnum); // access elems through iters ccxll_free(list); // destroy the list after use 

它与STL的语法非常相似。 声明列表时确定list类型。 我们无需担心类型安全性,因为在对列表执行操作时没有不安全的类型转换。