我想做的事情如下:

enum E;

void Foo(E e);

enum E {A, B, C};

编译器拒绝它。我在谷歌上快速浏览了一下,共识似乎是“你做不到”,但我不明白为什么。有人能解释一下吗?

澄清2:我这样做是因为我在一个类中有私有方法,它采用所说的枚举,我不希望枚举的值暴露出来-因此,例如,我不希望任何人知道E被定义为

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

项目X不是我想让我的用户知道的东西。

所以,我想要转发声明枚举,这样我就可以把私有方法放在头文件中,在cpp中内部声明枚举,并将构建的库文件和头分发给人们。

至于编译器——它是GCC。


当前回答

在GCC中似乎不能前向声明!

这里有一个有趣的讨论。

其他回答

因为这个被撞了(某种程度上),所以有一些不同意见,所以这里有一些来自标准的相关比特。研究表明,该标准并没有真正定义前向声明,也没有明确说明枚举可以或不可以前向声明。

首先,从dcl。Enum,第7.2节:

The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.

因此枚举的底层类型是由实现定义的,有一个较小的限制。

接下来,我们翻到关于“不完整类型”的章节(3.9),这是关于前向声明的最接近的标准:

A class that has been declared but not defined, or an array of unknown size or of incomplete element type, is an incompletely-defined object type. A class type (such as "class X") might be incomplete at one point in a translation unit and complete later on; the type "class X" is the same type at both points. The declared type of an array object might be an array of incomplete class type and therefore incomplete; if the class type is completed later on in the translation unit, the array type becomes complete; the array type at those two points is the same type. The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points ("array of unknown bound of T" and "array of N T") are different types. The type of a pointer to array of unknown size, or of a type defined by a typedef declaration to be an array of unknown size, cannot be completed.

在这里,标准列出了可以前向声明的类型。Enum不存在,因此编译器作者通常认为标准不允许前向声明,因为其底层类型的大小是可变的。

这也说得通。枚举通常在按值的情况下引用,编译器确实需要知道这些情况下的存储大小。由于存储大小是实现定义的,许多编译器可能只选择为每个枚举的底层类型使用32位值,这时就可以转发声明它们。

一个有趣的实验可能是尝试在Visual Studio中向前声明枚举,然后强制它使用大于sizeof(int)的底层类型,看看会发生什么。

在c++中向前声明是非常有用的,因为它极大地加快了编译时间。你可以在c++中前向声明一些东西,包括:结构,类,函数,等等…

但是你能在c++中前向声明一个枚举吗?

不,你不能。

但为什么不允许呢?如果允许的话,你可以在头文件中定义枚举类型,在源文件中定义枚举值。听起来应该是允许的,对吧?

错了。

在c++中,没有像c# (int)中那样的enum默认类型。在c++中,你的枚举类型将由编译器决定为适合你的枚举值范围的任何类型。

这是什么意思?

这意味着在定义了枚举的所有值之前,不能完全确定枚举的底层类型。这意味着您不能将枚举的声明和定义分开。因此,在c++中不能前向声明枚举。

ISO c++标准S7.2.5:

The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.

在c++中,可以使用sizeof操作符确定枚举类型的大小。枚举类型的大小是其基础类型的大小。通过这种方式,您可以猜测编译器正在为枚举使用哪种类型。

如果你像这样显式地指定枚举的类型会怎样:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

然后你可以向前声明你的枚举吗?

不。但为什么不呢?

指定枚举的类型实际上不是当前c++标准的一部分。它是一个vc++扩展。不过,它将成为c++ 0x的一部分。

在GCC中似乎不能前向声明!

这里有一个有趣的讨论。

对于vc++,下面是关于前向声明和指定底层类型的测试:

下面的代码编译成功。

    typedef int myint;
    enum T ;
    void foo(T * tp )
    {
        * tp = (T)0x12345678;
    }
    enum T : char
    {
        A
    };

但是我得到了/W4的警告(/W3不招致这个警告)

使用非标准扩展名:为枚举“T”指定底层类型

vc++ (Microsoft (R) 32位C/ c++优化编译器版本15.00.30729.01 for 80x86)在上述情况下看起来有bug:

当看到枚举T时;VC假设枚举类型T使用默认的4字节int作为底层类型,因此生成的程序集代码为:

    ?foo@@YAXPAW4T@@@Z PROC                    ; foo
    ; File e:\work\c_cpp\cpp_snippet.cpp
    ; Line 13
        push    ebp
        mov    ebp, esp
    ; Line 14
        mov    eax, DWORD PTR _tp$[ebp]
        mov    DWORD PTR [eax], 305419896        ; 12345678H
    ; Line 15
        pop    ebp
        ret    0
    ?foo@@YAXPAW4T@@@Z ENDP                    ; foo

上面的汇编代码是从/Fatest中提取的。这不是我个人的猜测。

你看到了吗

mov DWORD PTR[eax], 305419896        ; 12345678H

行吗?

下面的代码片段证明了这一点:

    int main(int argc, char *argv)
    {
        union {
            char ca[4];
            T t;
        }a;
        a.ca[0] = a.ca[1] = a.[ca[2] = a.ca[3] = 1;
        foo( &a.t) ;
        printf("%#x, %#x, %#x, %#x\n",  a.ca[0], a.ca[1], a.ca[2], a.ca[3] );
        return 0;
    }

结果是:

0x78, 0x56, 0x34, 0x12

删除枚举T的前向声明,并将函数foo的定义移动到枚举T的定义之后:结果为OK:

上面的键指令变成:

mov BYTE PTR [eax], 120;00000078 h

最终结果为:

0x78, 0x1, 0x1, 0x1

注意,该值没有被覆盖。

因此,在vc++中使用前向声明enum被认为是有害的。

顺便说一句,毫不奇怪,底层类型的声明语法与c#中的相同。在实践中,我发现在与内存有限的嵌入式系统通信时,通过将底层类型指定为char来节省三个字节是值得的。

可以将枚举包装在结构体中,添加一些构造函数和类型转换,然后前向声明结构体。

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

这似乎是有效的: http://ideone.com/TYtP2