如何创建类型安全的枚举? [英] How to create type safe enums?
问题描述
在 C 中使用枚举实现类型安全是有问题的,因为它们本质上只是整数.并且枚举常量实际上被标准定义为 int
类型.
To achieve type safety with enums in C is problematic, since they are essentially just integers. And enumeration constants are in fact defined to be of type int
by the standard.
为了实现一点类型安全,我用这样的指针做技巧:
To achieve a bit of type safety I do tricks with pointers like this:
typedef enum
{
BLUE,
RED
} color_t;
void color_assign (color_t* var, color_t val)
{
*var = val;
}
因为指针的类型规则比值更严格,所以这可以防止这样的代码:
Because pointers have stricter type rules than values, so this prevents code such as this:
int x;
color_assign(&x, BLUE); // compiler error
但它不会阻止这样的代码:
But it doesn't prevent code like this:
color_t color;
color_assign(&color, 123); // garbage value
这是因为枚举常量本质上只是一个 int
并且可以隐式分配给枚举变量.
This is because the enumeration constant is essentially just an int
and can get implicitly assigned to an enumeration variable.
有没有办法编写这样的函数或宏color_assign
,即使对于枚举常量也能实现完整的类型安全?
Is there a way to write such a function or macro color_assign
, that can achieve complete type safety even for enumeration constants?
推荐答案
可以通过一些技巧来实现这一点.给定
It is possible to achieve this with a few tricks. Given
typedef enum
{
BLUE,
RED
} color_t;
然后定义一个不会被调用者使用的虚拟联合,但包含与枚举常量同名的成员:
Then define a dummy union which won't be used by the caller, but contains members with the same names as the enumeration constants:
typedef union
{
color_t BLUE;
color_t RED;
} typesafe_color_t;
这是可能的,因为枚举常量和成员/变量名称驻留在不同的命名空间中.
This is possible because enumeration constants and member/variable names reside in different namespaces.
然后制作一些类似函数的宏:
Then make some function-like macros:
#define c_assign(var, val) (var) = (typesafe_color_t){ .val = val }.val
#define color_assign(var, val) _Generic((var), color_t: c_assign(var, val))
这些宏的调用方式如下:
These macros are then called like this:
color_t color;
color_assign(color, BLUE);
说明:
- C11
_Generic
关键字确保枚举变量的类型正确.但是,这不能用于枚举常量BLUE
,因为它属于int
类型. - 因此辅助宏
c_assign
创建了一个虚拟联合的临时实例,其中指定的初始化语法用于将值BLUE
分配给名为蓝色代码>.如果不存在这样的成员,代码将无法编译.
- 然后将相应类型的联合成员复制到枚举变量中.
- The C11
_Generic
keyword ensures that the enumeration variable is of the correct type. However, this can't be used on the enumeration constantBLUE
because it is of typeint
. - Therefore the helper macro
c_assign
creates a temporary instance of the dummy union, where the designated initializer syntax is used to assign the valueBLUE
to a union member namedBLUE
. If no such member exists, the code won't compile. - The union member of the corresponding type is then copied into the enum variable.
我们实际上不需要辅助宏,我只是为了可读性而拆分了表达式.写起来也一样好
We actually don't need the helper macro, I just split the expression for readability. It works just as fine to write
#define color_assign(var, val) _Generic((var),
color_t: (var) = (typesafe_color_t){ .val = val }.val )
<小时>
示例:
color_t color;
color_assign(color, BLUE);// ok
color_assign(color, RED); // ok
color_assign(color, 0); // compiler error
int x;
color_assign(x, BLUE); // compiler error
typedef enum { foo } bar;
color_assign(color, foo); // compiler error
color_assign(bar, BLUE); // compiler error
<小时>
编辑
显然,上面的内容并不能阻止调用者简单地输入color =garbage;
.如果您希望完全阻止使用枚举的这种分配的可能性,您可以将它放在一个结构中,并使用带有 "opaque type" 的私有封装的标准过程:
Obviously the above doesn't prevent the caller from simply typing color = garbage;
. If you wish to entirely block the possibility of using such assignment of the enum, you can put it in a struct and use the standard procedure of private encapsulation with "opaque type":
颜色.h
#include <stdlib.h>
typedef enum
{
BLUE,
RED
} color_t;
typedef union
{
color_t BLUE;
color_t RED;
} typesafe_color_t;
typedef struct col_t col_t; // opaque type
col_t* col_alloc (void);
void col_free (col_t* col);
void col_assign (col_t* col, color_t color);
#define color_assign(var, val)
_Generic( (var),
col_t*: col_assign((var), (typesafe_color_t){ .val = val }.val)
)
颜色.c
#include "color.h"
struct col_t
{
color_t color;
};
col_t* col_alloc (void)
{
return malloc(sizeof(col_t)); // (needs proper error handling)
}
void col_free (col_t* col)
{
free(col);
}
void col_assign (col_t* col, color_t color)
{
col->color = color;
}
main.c
col_t* color;
color = col_alloc();
color_assign(color, BLUE);
col_free(color);
这篇关于如何创建类型安全的枚举?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!