【问题标题】:How to emulate strongly typed enum in C?如何在 C 中模拟强类型枚举?
【发布时间】:2012-08-07 20:54:55
【问题描述】:

在 C++03 中,可以通过将 strongly typed enum 放入类(或命名空间)中来模拟它:

struct MyEnum
{
  enum enumName
  {
    VALUE_1 = 1,
    VALUE_2,
  };
};

并使用它:

MyEnum::enumName v = MyEnum::VALUE_1;

是否可以在 C 中做类似的事情?如果是,怎么做?


我试过这样,但当然不行:

struct A
{
  enum aa
  {
    V1 = 5
  };
};

int main()
{
  A::aa a1 = A::V1;
  enum A::aa a2 = A::V1;
  struct A::aa a3 = A::V1;

  return 0;
}

【问题讨论】:

  • C 中没有命名空间和类 - 所以你不能......
  • C99 中的可变参数宏可能与一些事情有关,通过将范围运算符替换为下划线。但在普通的 C89 中,我认为这是不可能的。
  • @AJG85:C 中的结构与 C++ 中的结构不同,在 C++ 中它们是,而在 C 中它们不是。
  • @Morwenn C99 解决方案很好
  • @tuxtimo:实际上,C 确实为标识符提供了四种不同的命名空间。一个用于标签,一个用于结构、联合和枚举标签,一个用于结构和联合成员,最后一个用于所有其他标识符,包括枚举常量.

标签: c enums


【解决方案1】:

由于 C 不提供命名空间,因此您可以使用前缀。

enum MyEnum {
    MyEnumA = 1,
    MyEnumB,
    MyEnumC
};

enum OtherEnum {
    OtherEnumA = 1,
    OtherEnumB
};

然后,为了变量声明的简洁,你可以为你的枚举声明类型,像这样:

typedef enum MyEnum MyEnum;
typedef enum OtherEnum OtherEnum;

最后,如果您不想允许将 OtherEnumB 隐式转换为 MyEnum 类型,Clang 提供了 -Wenum-conversion 标志(不幸的是,我认为 GCC 中没有类似的标志)。

/tmp/test.c:24:20: warning: implicit conversion from enumeration type 'enum OtherEnum' to different enumeration type 'MyEnum' (aka 'enum MyEnum') [-Wenum-conversion]
    MyEnum value = OtherEnumB;
           ~~~~~   ^~~~~~~~~~
1 warning generated.

这具有简单易懂的优点,并且可以很好地与您的(至少是我的)IDE 的自动完成功能配合使用。

【讨论】:

  • 我知道已经是 3 年了,所以如果没有回复也不用担心。你用的是g++吗?使用 gcc,我无法完成这项工作。我试过 4.6 和 4.7 版本,还添加了 -Wextra、-Wall -Wenum-compare。
  • 我使用了 Clang。我认为 GCC 也会针对 enum 类型之间的隐式转换发出警告,但我可能错了。看起来 GCC 只在使用 -Wenum-compare 时发出比较警告。
  • 啊,对。它的工作原理与您对 clang 的描述相同。我还尝试了更新的 gcc,但仍然没有警告。也有错误报告和讨论,但 gcc 开发人员似乎对现状感到满意,因为“标准说这一切都是有效的”。
【解决方案2】:

这是我的解决方案。与@Eric 的设计相比有一些优势:

  • 支持相等性测试(例如A_VALUE_0 == value
  • 不依赖于 C99 的复合文字
  • 可以强制转换为枚举分配不正确的值。

缺点:

  • 标志不起作用(例如A_VALUE_0 | A_VALUE_1
  • 不能是switch'd
  • 在测试相等性时可能会混淆 IDE 错误行的位置(例如 A_VALUE_0 == B_VALUE_1

注意事项:

  • 永远不要取消引用这种类型的指针。会比兰博基尼更快地导致崩溃

这是实现(使用-Werror & -pedantic 编译):

typedef struct A { char empty[1]; } *A; // we use 'empty' so that we don't get a warning that empty structs are a GNU extension
#define A_VALUE_0 ((A) 0x1)
#define A_VALUE_1 ((A) 0x2)
#define A_VALUE_2 ((A) 0x4)

typedef struct B { char empty[1]; } *B;

#define B_VALUE_0 ((B) 0x0)
#define B_VALUE_1 ((B) 0x1)
#define B_VALUE_2 ((B) 0x2)

int main()
{
    A a = A_VALUE_0;

    int equal = (a == A_VALUE_1); // works!
    int euqal = (a == B_VALUE_1) // doesn't work

    A flags = A_VALUE_0 | A_VALUE_1; // doesn't work!

    switch (a) { // doesn't work
        case A_VALUE_0:
            puts("value 0");
            break;
        case A_VALUE_1:
            puts("value 1");
            break;
        case A_VALUE_2:
            puts("value 2");
            break;
        default:
            puts("unknown value");
            break;
    } // doesn't work

    // casting works for assignment:
    A b = (A) (B_VALUE_2);

    return 0;
}

【讨论】:

  • +1,一些 cmets:我仍然会在底部使用 enum 来声明值。如果添加值,这更容易维护,例如为避免某些老式编译器出现令人毛骨悚然的整数指针警告,您应该使用uintptr_t,类似于((B)(uintptr_t) 0x2)。要声明指向 struct 的指针,您甚至不必声明结构本身,typedef struct spooky_struct* A 就足够了。
  • 这种方法依赖于协作实现定义的行为“整数可以转换为任何指针类型。...,结果是实现定义的,可能没有正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。” C17dr § 6.3.2.3 5 边缘编码。
【解决方案3】:

你可以这样做:

// Declare A to use for an enumeration, and declare some values for it.
typedef struct { int i; } A;
#define A0  ((A) { 0 })
#define A1  ((A) { 1 })

// Declare B to use for an enumeration, and declare some values for it.
typedef struct { int i; } B;
#define B0  ((B) { 0 })
#define B1  ((B) { 1 })


void foo(void)
{
    // Initialize A.
    A a = A0;

    // Assign to A.
    a = A1;

    // Assign a value from B to A.
    a = B0; // Gets an error.
}

这会给您一些输入,但可能会很麻烦,具体取决于您要对枚举及其值执行的其他操作。

【讨论】:

  • 作业使用了 C99 功能 (compound literals),但其余部分也适用于 C89。
  • @Shahbaz 这对我来说与gcc -ansi(使用 gcc 4.1.2)一起工作。
  • @rwos,是的。我现在记得我无法编写它(在我的某些代码中),因为我正在使用 g++ 编译,而不是它是 C11。我的错。
  • 这里最大的问题是相等性测试不起作用,即A a = A0 ; A b = A0 ; bool foo = (a == b); 无法编译。此外,您不能在开关中使用类型 A 的变量。
  • @EricFinn gcc 和 -ansi -pedantic 显示警告(“ISO C90 禁止复合文字”)。另一方面:谁再使用 C89/90-only 编译? :-)
猜你喜欢
  • 2012-09-16
  • 1970-01-01
  • 2011-03-03
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多