具有宏的类型安全的通用容器 [英] Type-safe generic containers with macros
问题描述
我试图让使用C中宏的类型安全的通用链表.它应该的工作方式类似于模板用C如何工作++.例如,
I'm trying to make a type-safe generic linked list in C using macros. It should work similarly to how templates work in C++. For example,
LIST(int) *list = LIST_CREATE(int);
我的第一次尝试是为
My first attempt was for #define LIST(TYPE)
(the macro I used above) to define a struct _List_##TYPE {...}
. That, however, did not work because the struct would be redefined every time I declared a new list. I remedied the problem by doing this:
/* 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)
的文件,并且其中一些相互包含时,同一结构仍然会有多个定义.有没有什么办法让
But another problem is that when I have multiple files that use DEFINE_LIST(int)
, for example, and some of them include each other, then there will still be multiple definitions of the same struct. Is there any way to make DEFINE_LIST
check if the struct has already been defined?
/* one.h */
DEFINE_LIST(int);
/* two.h */
#include "one.h"
DEFINE_LIST(int); /* Error: already defined in one.h */
推荐答案
在C ++获取模板之前,我已经用C解决了这个问题,但我仍然 有代码.
I tackled this problem in C before C++ acquired templates and I still have code.
您不能定义一个容器的-T真正泛型类型安全模板与宏 在这是完全局限在头文件的方式.该标准预处理 提供推"和啪"的宏观分配你将没有办法 所以需要通过嵌套和顺序为保持其完整性 扩张的环境.你会马上遇到嵌套的背景下,你 尝试通过定义T容器来吃自己的狗粮.
You can't define a truly generic typesafe container-of-T template with macros in a way that's confined entirely to header files. The standard preprocessor provides no means of "pushing" and "popping" the macro assignments you will require so as preserve their integrity through nested and sequential contexts of expansion. And you will encounter nested contexts as soon as you try to eat your own dog food by defining a container-of-containers-of-T.
这是可以完成的,正如我们将要看到的,但是正如@immortal所暗示的那样,它必然导致
产生不同的
The thing can be done, as we'll see, but as @immortal suggests, it entails
generating distinct .h
and .c
files for each value of T that you require.
You can, for example, define a completely generic list-of-T with macros in
an inline file, say, list_type.inl
, and then include list_type.inl
in a
each of pair of small set-up wrappers - list_float.h
and list_float.c
- that
will respectively define and implement the list-of-float container. Similarly
for list-of-int, list-of-list-of-float, list-of-vector-of-list-of-double,
and so so.
一个示意性示例将使所有清楚.但首先只得到充分措施 吃自己的狗粮的挑战.
A schematic example will make all clear. But first just get the full measure of the eat-your-own-dogfood challenge.
考虑这样的二阶容器作为列表的-列表-的-thingummy.我们想 能够通过T设置为实例化这些=列表的-thingummy我们的宏观 列表的-T解决方案.但绝不是-的列表thingummy将是一个POD 数据类型.无论是我们自己的狗粮还是其他人的狗粮, 将是一个抽象的数据类型,它住在堆和表示,以 它的用户通过一个typedef-ED指针类型.或者最起码,这是怎么回事 会对堆举行动态组件.在任何情况下,不POD.
Consider such a second-order container as a list-of-lists-of-thingummy. We want to be able to instantiate these by setting T = list-of-thingummy for our macro list-of-T solution. But in no way is list-of-thingummy going to be a POD datatype. Whether list-of-thingummy is our own dogfood or somebody else's, it's going to be an abstract datatype that lives on the heap and is represented to its users through a typedef-ed pointer type. Or at the very least, it is going to have dynamic components held on the heap. In any case, not POD.
这意味着它是不够的列表的-T我们的解决方案只是被告知, 列表的-thingummy T =.还必须告诉T是否需要非POD 复制构建和销毁,以及如何复制和销毁 一.用C表示,这意味着:
This means it's not enough for our list-of-T solution just to be told that T = list-of-thingummy. It must also be told whether a T requires non-POD copy-construction and destruction, and if so how to copy-construct and destroy one. In C terms, that means:
-
复制施工:如何在一个T-尺寸创建一个给定T的拷贝 未提交的存储器的区域,给定的这样的区域的地址.
Copy-construction: How to create a copy of a given T in a T-sized region of uncommitted memory, given the address of such a region.
销毁:如何销毁给定地址上的T.
Destruction: How to destroy the T at a given address.
我们的可以的根本不知道有关默认建筑或建筑从 非T的参数,我们可以合理地限制我们的列表的-T解决方案 从用户提供的原稿复制的对象的容纳.但是,我们做 必须复制他们,我们必须处理我们的份.
We can do without knowing about default construction or construction from non-T parameters, as we can reasonably restrict our list-of-T solution to the containment of objects copied from user-supplied originals. But we do have to copy them, and we have to dispose of our copies.
接下来,假设我们渴望提供设定的-T的模板,或地图的-T1到T2,
除了列表的-T.这些关键排序的数据类型添加其他参数
我们将在堵塞的T或T1,任何非POD值即如何才能
键类型:任何两个对象.事实上,我们将需要为参数
任何键数据类型,其
Next, suppose that we aspire to offer a template for set-of-T, or map-of-T1-to-T2,
in addition to list-of-T. These key-ordered datatypes add another parameter
we will have to plug in for any non-POD value of T or T1, namely how to order
any two objects of the key type. Indeed we will need that parameter for
any key datatype for which memcmp()
won't do.
已经指出的是,我们将用列表的-T的简单问题的坚持
示意性例子;并为进一步简化,我会忘记可取
任何const
API的
Having noted that, we'll stick with the simpler list-of-T problem for the
schematic example; and for further simplicity I'll forget the desirability
of any const
API.
对于此类型和任何其他模板容器类型,我们需要一些令牌粘贴 宏,使我们可以方便地组合函数和类型的标识符, 再加上可能还有其它工具宏.这些都可以去一个头,说<12> 如:
For this and any other template container type we'll want some token-pasting
macros that let us conveniently assemble identifiers of functions and types,
plus probably other utility macros. These can all go in a header, say macro_kit.h
,
such as:
#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 <stddef.h>
#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.
N.B. 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`.
N.B. This file itself defines the destructor for the list-type that
it generates.
*/
/* Define the names of the list-type to generate,
e.g. 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,
e.g 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
请注意
Note that list_type.inl
has no macro-guard against mutliple inclusion. You want
at least some of it - at least the template API - to be included every time it is
seen.
如果您在阅读文件顶部的评论,你可以猜到你会怎么代码 的包装标头导入一个列表的-INT的容器类型.
If you read the comments at the top of the file you can guess how you would code a wrapping header to import a list-of-int container type.
#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
和同样你会如何编写包裹头部导入列表的一览的-INT 容器类型:
and likewise how you would code the wrapping header to import a list-of-list-of-int container type:
#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
您的应用程序可以安全地包括这样的包装,e.g.
Your applications can safely include such wrappers, e.g.
#include "list_int.h"
#include "list_list_int.h"
尽管它们以冲突的方式定义LIST_ELEMENT_TYPE
的事实是因为
list_type.inl
总是#undefs
所有参数化列表类型的宏
当它与他们所做的:看到文件的最后几行
despite the fact the they define LIST_ELEMENT_TYPE
in conflicting ways because
list_type.inl
always #undefs
all the macros that parameterize the list-type
when it's done with them: see the last few lines of the file.
请注意过使用宏
Note too the use of the macro LIST_IMPLEMENT
. If its undefined when list_type.inl
is parsed then only the template API is exposed; the template implementation is
skipped. If LIST_IMPLEMENT
is defined then the whole file is compiled. Thus our
wrapping headers, by not defining LIST_IMPLEMENT
, import only the list-type
API.
相反我们的包装源文件<22>,
Conversely for our wrapping source files list_int.c
, list_list_int.c
, we will
define LIST_IMPLEMENT
. After that, there's nothing to do but include the
corresponding header:
/* 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"
现在在你的应用程序,没有列表模板的宏出现.您的包裹 标头解析为真实代码":
Now in your application, no list-template macros appear. Your wrapping headers parse out to "real code":
#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
包装器
其他类型的按需
To equip yourself with a "C template library" this way the only (!) hard work
is to write the .inl
file for each container type you want and to test it
very, very thoroughly. You would then probably generate an object file
and header for each combination of native datatype and container type for
off-the-shelf linkage, and knock out the .h
and .c
wrappers in a jiffy for
other types on demand.
不用说,只要C ++发芽模板我enthusiam出汗 出来这样蒸发.不过,这是可以做到这样,完全 通常,如果出于某种原因,C是唯一的选择.
Needless to say, as soon as C++ sprouted templates my enthusiam for sweating them out this way evaporated. But it can be done this way, completely generically, if for some reason C is the only option.
这篇关于具有宏的类型安全的通用容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!