如何创建类型安全枚举? [英] How to create type safe enums?

查看:121
本文介绍了如何创建类型安全枚举?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了实现C中枚举的类型安全性是有问题的,因为它们本质上只是整数。枚举常数实际上是按照标准定义为 int 的类型。



要实现一些类型安全我用这样的指针来做点:

  typedef枚举
{
BLUE,
RED
} color_t;

void color_assign(color_t * var,color_t val)
{
* var = val;
}

因为指针比值更严格的类型规则,所以这样可以防止这样的代码:

  int x; 
color_assign(& x,BLUE); //编译器错误

但是它不会阻止这样的代码:

  color_t color; 
color_assign(& color,123); //垃圾值

这是因为枚举常量本质上只是一个 int ,并可以隐式分配给枚举变量。



有没有办法编写这样一个函数或宏 color_assign ,即使枚举也能实现完整的类型安全常数?

解决方案

可以通过一些技巧来实现这一点。给定

  typedef枚举
{
BLUE,
RED
} color_t;

然后定义一个不会被调用者使用的虚拟联合,但包含相同的成员名称作为枚举常数:

  typedef union 
{
color_t BLUE;
color_t RED;
} typesafe_color_t;

这是可能的,因为枚举常量和成员/变量名称驻留在不同的命名空间中。



然后制作一些类似功能的宏:

  #define c_assign(var,val) (var var,val)= $($)
$ b #define color_assign(var,val)_Generic((var),color_t:c_assign(var,val))

然后这些宏被调用如下:

  color_t color; 
color_assign(color,BLUE);

说明:




  • C11 _Generic 关键字确保枚举变量是正确的类型。但是,它不能用于枚举常量 BLUE ,因为它的类型为 int

  • 因此,辅助宏 c_assign 创建一个虚拟联合的临时实例,其中使用指定的初始化器语法分配值 BLUE 到名为 BLUE 的工会成员。如果没有这样的成员,代码将不会编译。

  • 然后将相应类型的union成员复制到枚举变量中。



我们实际上不需要帮助宏,我只是拆分表达式以便阅读。

  #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); //编译器错误

int x;
color_assign(x,BLUE); //编译器错误

typedef枚举{foo} bar;
color_assign(color,foo); //编译器错误
color_assign(bar,BLUE); //编译器错误






编辑



显然,上述不能阻止调用者简单地键入 color = garbage; 。如果您希望完全阻止使用枚举这种分配的可能性,则可以将其放在一个结构体中,并使用opaque type的私有封装的标准过程:



color.h

  #include< stdlib.h> 

typedef枚举
{
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)\

color.c

  #includecolor.h

struct col_t
{
color_t color;
};

col_t * col_alloc(void)
{
return malloc(sizeof(col_t)); //(需要正确的错误处理)
}

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);


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

This is because the enumeration constant is essentially just an int and can get implicitly assigned to an enumeration variable.

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); 

Explanation:

  • The C11 _Generic keyword ensures that the enumeration variable is of the correct type. However, this can't be used on the enumeration constant BLUE because it is of type int.
  • Therefore the helper macro c_assign creates a temporary instance of the dummy union, where the designated initializer syntax is used to assign the value BLUE to a union member named BLUE. 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 )


Examples:

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


EDIT

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":

color.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) \
  )

color.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆