我想做的事情如下:

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。


当前回答

[我的答案是错误的,但我把它留在这里,因为评论很有用]。

前向声明枚举是非标准的,因为指向不同枚举类型的指针不能保证大小相同。编译器可能需要查看定义才能知道该类型可以使用哪些大小指针。

实际上,至少在所有流行的编译器上,指向枚举的指针的大小是一致的。例如,枚举的前向声明是由Visual c++作为语言扩展提供的。

其他回答

对于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来节省三个字节是值得的。

如果您真的不希望枚举出现在头文件中,并确保它只被私有方法使用,那么一种解决方案是遵循PIMPL原则。

这是一种确保在头文件中隐藏类内部信息的技术,只需声明:

class A
{
public:
    ...
private:
    void* pImpl;
};

然后在实现文件(.cpp)中声明一个类,该类将作为内部结构的表示。

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

你必须在类构造函数中动态创建实现,并在析构函数中删除它,当实现公共方法时,你必须使用:

((AImpl*)pImpl)->PrivateMethod();

使用PIMPL有优点。一个是它将类头与其实现解耦,并且在更改一个类实现时不需要重新编译其他类。另一个原因是加快了你的编译时间,因为你的头文件太简单了。

但它使用起来很痛苦,所以你应该问问自己,仅仅在头文件中将枚举声明为private是否有那么大的麻烦。

[我的答案是错误的,但我把它留在这里,因为评论很有用]。

前向声明枚举是非标准的,因为指向不同枚举类型的指针不能保证大小相同。编译器可能需要查看定义才能知道该类型可以使用哪些大小指针。

实际上,至少在所有流行的编译器上,指向枚举的指针的大小是一致的。例如,枚举的前向声明是由Visual c++作为语言扩展提供的。

定义一个枚举来将类型元素的可能值限制在一个有限的集合内。该限制将在编译时强制执行。

当向前声明你将在以后使用“有限的集合”这一事实时,不会增加任何价值:后续代码需要知道可能的值,以便从中受益。

尽管编译器关心枚举类型的大小,但当转发声明枚举时,枚举的意图就会丢失。

事实上,没有所谓的提前宣布全体会议。由于枚举的定义不包含任何可能依赖于使用该枚举的其他代码的代码,因此在第一次声明枚举时完全定义它通常不是问题。

如果枚举的唯一用途是私有成员函数,则可以通过将枚举本身作为该类的私有成员来实现封装。枚举仍然必须在声明时完全定义,即在类定义中。然而,与在那里声明私有成员函数相比,这并不是一个更大的问题,也没有比这更糟糕的实现内部公开了。

如果您需要对实现细节进行更深层次的隐藏,您可以将其分解为一个抽象接口(仅由纯虚函数组成)和一个具体的、完全隐藏的实现(继承)接口的类。类实例的创建可以由工厂或接口的静态成员函数处理。这样,即使是真正的类名也不会被暴露,更不用说它的私有函数了。