我想做的事情如下:

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中似乎不能前向声明!

这里有一个有趣的讨论。


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

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

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


因为枚举可以是不同大小的整型(编译器决定给定枚举的大小),所以指向枚举的指针也可以是不同大小的,因为它是整型类型(例如,在某些平台上字符的指针大小不同)。

所以编译器甚至不能让你前向声明枚举并使用指向它的指针,因为即使在那里,它也需要枚举的大小。


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

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

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


我会这样做:

[在公共标头中]

typedef unsigned long E;

void Foo(E e);

[在内部头文件中]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

通过添加FORCE_32BIT,我们可以确保Econtent编译成一个long,这样它就可以和E。


enum不能被前向声明的原因是,如果不知道值,编译器就不能知道enum变量所需的存储空间。c++编译器可以根据包含所有指定值所需的大小指定实际存储空间。如果所有可见的都是前向声明,那么翻译单元就不能知道所选择的存储大小是什么——它可能是char型、int型或其他类型。


ISO c++标准第7.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++ 0X中,已经提出并接受了向前声明enum类型的语法。你可参阅前向申报枚举数(修订版3)的建议


我对你问题的解决方案是:

1 -使用int代替enums:在CPP文件的匿名命名空间中声明你的int(而不是在头文件中):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

因为你的方法是私有的,所以没有人会乱动数据。你甚至可以进一步测试是否有人发送了无效的数据:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2:像在Java中那样,用有限的const实例化创建一个完整的类。向前声明类,然后在CPP文件中定义它,并只实例化类似枚举的值。我在c++中做了类似的事情,结果并不像预期的那样令人满意,因为它需要一些代码来模拟枚举(复制构造,operator =等)。

3:如前所述,使用私有声明的enum。尽管用户将看到它的完整定义,但它不能使用它,也不能使用私有方法。因此,您通常可以修改枚举和现有方法的内容,而不需要使用您的类重新编译代码。

我猜答案不是3就是1。


如果您真的不希望枚举出现在头文件中,并确保它只被私有方法使用,那么一种解决方案是遵循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是否有那么大的麻烦。


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

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

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


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

首先,从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)的底层类型,看看会发生什么。


我只是注意到,原因实际上是,在向前声明后,enum的大小还不知道。你使用一个结构的前向声明来传递一个指针,或者从一个在前向声明的结构定义本身中引用的地方引用一个对象。

向前声明枚举不是很有用,因为人们希望能够按值传递枚举。你甚至不能有指向它的指针,因为我最近被告知一些平台使用不同大小的指针用于char和int或long。所以这一切都取决于枚举的内容。

当前的c++标准明确禁止这样做

enum X;

(在7.1.5.3/1)。但下一个c++标准将于明年发布,允许以下功能,这让我确信问题实际上与底层类型有关:

enum X : int;

它被称为“不透明”枚举声明。您甚至可以在以下代码中按值使用X。并且它的枚举数可以稍后在枚举的重新声明中定义。参见当前工作草案中的7.2。


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


在我的项目中,我采用了命名空间限定枚举技术来处理来自遗留组件和第三方组件的枚举。这里有一个例子:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo。:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

注意foo.h头文件不需要知道任何关于legacy::evil的信息。只有使用遗留类型legacy::evil(这里:main.cc)的文件才需要包含枚举.h。


从c++ 11开始,枚举的前向声明是可能的。以前,枚举类型不能前向声明的原因是枚举的大小取决于它的内容。只要应用程序指定了枚举的大小,它就可以被前向声明:

enum Enum1;                     // Illegal in C++03 and C++11; no size is explicitly specified.
enum Enum2 : unsigned int;      // Legal in C++11.
enum class Enum3;               // Legal in C++11, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; // Legal C++11.
enum Enum2 : unsigned short;    // Illegal in C++11, because Enum2 was previously declared with a different type.

在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的一部分。


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

#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


在c++ 11中,你可以前向声明一个枚举,只要同时声明它的存储类型。语法如下所示:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

事实上,如果函数从未引用枚举的值,那么此时根本不需要完整的声明。

g++ 4.6及以后的版本(-std=c++0x或-std=c++11在最近的版本中)都支持这一点。Visual c++ 2013支持此功能;在早期版本中,它有一些我还没有弄清楚的非标准支持-我发现了一些建议,简单的向前声明是合法的,但您的哩数可能会有所不同。


对于iOS/Mac/Xcode上面临这种情况的人,

如果你在XCode中用Objective-C集成C/ c++头文件时遇到这个问题,只需将文件扩展名从。mm更改为。m即可


这样我们就可以forward declare enum

enum A: int;

详情请参阅连结。