在c++中向前声明枚举

我想做的事情如下:

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。

233357 次浏览

在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节:

枚举的基本类型是整型类型,可以表示枚举中定义的所有枚举值。使用哪个整型作为枚举的底层类型是由实现定义的,但底层类型不能大于int,除非枚举数的值不能适合intunsigned int。如果enumerator-list为空,则底层类型就好像枚举有一个值为0的枚举数。sizeof()的值应用于枚举类型、枚举类型的对象或枚举数,是应用于底层类型的sizeof()的值。

由于函数的调用者必须知道参数的大小才能正确设置调用堆栈,因此枚举列表中的枚举数必须在函数原型之前已知。

更新:

在c++ 0X中,已经提出并接受了向前声明enum类型的语法。你可以在枚举的前向声明(rev.3) .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节:

枚举的底层类型 是可以表示的整型吗 中定义的所有枚举值 枚举。它是 实现定义哪个积分 Type用作基础类型 对象的枚举除外 底层字体不应更大 而不是int,除非an的值 枚举数不能放入int或 无符号整数。如果枚举器列表 是否为空,底层类型是否如 枚举有一个单 值为0的枚举器。的价值 应用于枚举的Sizeof () Type,枚举类型的对象, 或枚举数,是的值 应用于底层的Sizeof () 类型。< / p >

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

接下来我们翻到“不完整的类型”一节。(3.9),这几乎是我们在前向声明方面最接近的标准:

已声明但未定义的类,或大小未知或大小未知的数组 不完全元素类型,是一个不完全定义的对象类型 类类型(如“class x”)在翻译过程中的某一点可能是不完整的 以后的单元和完成;“x类”类型;在这两点上都是相同的类型。的 数组对象的声明类型可能是不完整类类型和的数组 因此不完整;如果类类型在转换单元的后面完成, 数组类型变为完整;这两点上的数组类型是相同的类型。 数组对象的声明类型可能是大小未知的数组,因此为 翻译单元中不完整的,稍后完成的;数组类型为 这两点(t的未知界数组);和“N的数组”)是不同的 类型。指向未知大小的数组指针的类型,或由typedef定义的类型 声明为未知大小的数组,不能完成

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

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

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

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

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

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

enum X;

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

enum X : int;

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

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

  1. 下面的代码编译成功。
typedef int myint;
enum T ;
void foo(T * tp )
{
* tp = (T)0x12345678;
}
enum T : char
{
A
};

但是我得到了/W4的警告(/W3不会引起这个警告)

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

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

在我的项目中,我采用了Namespace-Bound枚举技术来处理来自遗留组件和第三方组件的__abc0。这里有一个例子:

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)的文件需要包含enum.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++中转发声明一些东西,包括:structclassfunction等等…

但是你能在c++中向前声明enum吗?

不,你不能。

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

错了。

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

这是什么意思?

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

ISO c++标准S7.2.5:

枚举的基础类型是整型,可以表示枚举中定义的所有枚举数值。使用哪个整型作为枚举的底层类型是由实现定义的,但底层类型不能大于int,除非枚举数的值不能适合intunsigned int。如果枚举器列表为空,则底层类型就像枚举有一个值为0的枚举器一样。sizeof()的值应用于枚举类型、枚举类型的对象或枚举数,是应用于底层类型的sizeof()的值。

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

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

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

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

不。但为什么不呢?

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

Source .

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

#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 < / p >

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

详情请参考链接