转发在c ++中声明一个枚举

我正在尝试做如下的事情:

enum E; void Foo(E e); enum E {A, B, C}; 

编译器拒绝。 我在Google上看了一下,看来共识似乎是“你做不到”,但我不明白为什么。 谁能解释一下? 非常感谢。

澄清2:我这样做,因为我有在一个类中的私有方法采取说枚举,我不希望枚举的值暴露 – 所以,例如,我不想让任何人知道E被定义为

 enum E { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X } 

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

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

至于编译器 – 这是GCC。

枚举不能被转发声明的原因是,如果不知道值,编译器不能知道枚举variables所需的存储空间。 C ++编译器允许根据包含所有指定值的大小来指定实际的存储空间。 如果所有可见的是前向声明,那么翻译单元就不知道select了什么样的存储大小 – 它可能是char或int,或者其他东西。


来自ISO C ++标准的第7.2.5节:

枚举的基本types是一个整型,可以表示枚举中定义的所有枚举值。 它是实现定义的,其中整型被用作枚举的基础types,除非基础types不应该大于int除非枚举器的值不能放在intunsigned int 。 如果枚举器列表为空,则基础types就好像枚举具有值为0的单个枚举器。应用于枚举types,枚举types对象或枚举器的sizeof()的值是sizeof()应用于底层types。

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

更新:在C ++ 0X中,已经提出并接受了前面声明枚举types的语法。 您可以在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf上看到提案;

C ++ 0x也可以向前声明枚举。 以前,枚举types不能被转发的原因是因为枚举的大小取决于它的内容。 只要枚举的大小由应用程序指定,就可以进行前向声明:

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

鉴于最近的发展,我在这里添加最新的答案。

您可以在C ++ 11中转发声明一个枚举,只要您同时声明其存储types即可。 语法如下所示:

 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支持这个; 在早期的版本中,我有一些非标准的支持,我还没有弄清楚 – 我发现一些build议,简单的前向声明是合法的,但是YMMV。

在C ++中向前声明事物是非常有用的,因为它大大加快了编译时间 。 你可以在C ++中转发声明几个东西,包括: structclassfunction等…

但你可以转发在C ++中声明一个enum

不,你不能。

但为什么不允许呢? 如果允许的话,你可以在你的头文件中定义你的enumtypes,并在源文件中定义你的enum值。 听起来像是应该允许的权利?

错误。

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

这意味着什么?

这意味着你的enum的底层types不能完全确定,直到你有所有的enum值的定义。 哪些人不能分开您的enum的声明和定义。 因此,你不能在C ++中转发声明一个enum

ISO C ++标准S7.2.5:

枚举的基本types是一个整型,可以表示枚举中定义的所有枚举值。 它是实现定义的,其中整型被用作枚举的基础types,除非基础types不应该大于int除非枚举器的值不能放在intunsigned int 。 如果枚举器列表为空,则基础types就好像该枚举具有值为0的单个枚举器。应用于枚举types,枚举types对象或枚举器的sizeof()的值是sizeof()应用于底层types。

您可以通过使用sizeof运算符来确定C ++中枚举types的sizeof 。 枚举types的大小是其基础types的大小。 通过这种方式,您可以猜出您的编译器使用哪种types的enum

如果你明确地指定你的enum的types,如下所示:

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

你可以然后前进宣布你的enum

不,但为什么不呢?

指定enumtypes实际上并不是当前C ++标准的一部分。 这是一个VC ++扩展。 它将成为C ++ 0x的一部分。

资源

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

向前声明枚举是非标准的,因为指向不同枚举types的指针不能保证是相同的大小。 编译器可能需要查看定义,以了解可以使用此types的指针大小。

实际上,至less在所有stream行的编译器中,枚举指针是一致的大小。 举例来说,枚举的前向声明是由Visual C ++提供的语言扩展。

确实没有这样的事情作为枚举的前瞻性声明。 由于枚举的定义不包含任何可能依赖于使用枚举的其他代码的代码,因此在首次声明枚举时完全定义枚举通常不是问题。

如果您的枚举的唯一用途是私有成员函数,您可以通过将枚举本身作为该类的私有成员来实现封装。 枚举仍然必须在声明处,即在类定义内完全定义。 但是,这并不是一个更大的问题,因为在那里声明私有成员函数并不是一个更糟糕的实现内部的暴露。

如果您需要对实现细节进行更深入的隐藏,则可以将其分解为抽象接口,只包含纯虚函数以及实现(inheritance)接口的具体的,完全隐藏的类。 类实例的创build可以由接口的工厂或静态成员函数来处理。 这样,即使是真正的类名,更不用说它的私有function,也不会被暴露。

我会这样做:

[在公众头上]

 typedef unsigned long E; void Foo(E e); 

[在内部标题]

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

通过添加FORCE_32BIT,我们确保Econtent编译到一个很长的时间,所以它可以与E互换。

只是注意到,原因实际上在前向声明之后还不知道枚举的大小。 那么你可以使用一个结构的前向声明来传递一个指针,或者引用一个对象,这个对象也来自前面声明的结构体定义本身。

向前声明一个枚举不会太有用,因为人们希望能够通过枚举来传递值。 你甚至不能有一个指针,因为我最近告诉一些平台使用不同大小的char指针而不是int或long。 所以这一切都取决于枚举的内容。

目前的C ++标准明确地不允许做类似的事情

 enum X; 

(在7.1.5.3/1 )。 但明年的下一个C ++标准允许下面这个,这让我确信这个问题实际上与底层types有关:

 enum X : int; 

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

看来它不能在GCC中提前申报!

这里有趣的讨论

如果你真的不希望你的枚举出现在你的头文件中,并确保它只被私有方法使用,那么一个解决scheme可以用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; }; 

您必须在类构造函数中dynamic创build实现,并在析构函数中将其删除,并且在实现公共方法时,必须使用:

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

有一些使用pimpl的优点,一个是它将你的类头部从它的实现中解耦出来,当改变一个类的实现时不需要重新编译其他的类。 另一个就是加速你的编译时间,因为你的头文件很简单。

但是使用起来很麻烦,所以你应该问问自己,如果只是在头文件中声明你的枚举是私有的,那就太麻烦了。

你可以将枚举包装在一个结构中,添加一些构造函数和types转换,然后转发声明结构。

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

这似乎工作: http : //ideone.com/TYtP2

我对您的问题的解决scheme将是:

1 – 使用int而不是枚举:在您的CPP文件(不在标题中)声明匿名命名空间中的整数:

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

由于你的方法是私人的,没有人会搞砸数据。 如果有人向您发送无效数据,您甚至可以进一步testing:

 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:用有限的const实例创build一个完整的类,就像在Java中完成的一样。 向前声明这个类,然后在CPP文件中定义它,并且只给出与枚举相似的值。 我在C ++中做了类似的事情,结果并不像期望的那样令人满意,因为它需要一些代码来模拟枚举(复制构造,运算符=等)。

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

我的猜测将是解决scheme3或1。

有一些不同意见,因为这被碰撞(有点),所以这里是一些标准的相关位。 研究表明,该标准并没有真正定义前向声明,也没有明确规定枚举可以或不可以被前向声明。

首先,从dcl.enum,第7.2节:

枚举的基本types是一个整型,可以表示枚举中定义的所有枚举值。 它是实现定义的,其中整型被用作枚举的基础types,除非基础types不应该大于int,除非枚举器的值不能放在int或unsigned int中。 如果枚举器列表为空,则基础types就好像该枚举具有值为0的单个枚举器。应用于枚举types,枚举types对象或枚举器的sizeof()的值是sizeof()应用于底层types。

所以枚举的基础types是实现定义的,只有一个小的限制。

接下来,我们翻到“不完全types”(3.9)的部分,这与我们就前向声明达成任何标准差不多。

已声明但尚未定义的类或未知大小或不完整元素types的数组是未完全定义的对象types。

一个类的types(比如“X类”)在翻译单元中的某一点可能是不完整的,稍后会完成; 两个点的types“X类”是相同的types。 数组对象的声明types可能是不完整的类types的数组,因此是不完整的; 如果稍后在翻译单元中完成类types,则数组types变为完整; 这两点的数组types是相同的types。 数组对象的声明types可能是未知大小的数组,因此在翻译单元中的某一点不完整,稍后完成; 这两个点的数组types(“T的未知数组”和“NT数组”)是不同的types。 指向未知大小数组的指针的types,或由typedef声明定义为未知大小数组的types指针的types不能完成。

所以在那里,这个标准几乎奠定了可以宣布的types。 枚举不在那里,所以编译器作者通常认为由于其基础types的可变大小而被声明为不被标准所拒绝。

这也是有道理的。 枚举通常在值types的情况下被引用,编译器确实需要在这些情况下知道存储的大小。 由于存储大小是实现定义的,因此许多编译器可能会select使用32位值作为每个枚举的基础types,此时可以转发声明它们。 一个有趣的实验可能是尝试在Visual Studio中向前声明一个枚举,然后强迫它使用大于sizeof(int)的基础types,如上所述,看看会发生什么。

对于VC,这里是关于前向声明和指定基础types的testing:

  1. 下面的代码是编译好的。
     typedef int myint;
    枚举T;
     void foo(T * tp)
     {
         * tp =(T)0x12345678;
     }
    枚举T:char
     {
        一个
     };

但是得到/ W4的警告(/ W3不会产生这个警告)

警告C4480:使用非标准扩展:指定枚举“T”的基础types

  1. VC(Microsoft(R)32位C / C ++优化编译器版本15.00.30729.01为80×86)在上述情况下看起来有问题:

    • 看到枚举T; VC假定枚举typesT使用默认的4字节int作为基础types,所以生成的汇编代码是:
     ?foo @@ YAXPAW4T @@@ Z PROC;  FOO
     ; 文件e:\ work \ c_cpp \ cpp_snippet.cpp
     ;  13号线
        推ebp
         mov ebp,尤指
     ;  14号线
         mov eax,DWORD PTR _tp $ [ebp]
         mov DWORD PTR [eax],305419896;  12345678H
     ;  15号线
        stream行ebp
         ret 0
     ?foo @@ YAXPAW4T @@@ Z ENDP;  FOO

上面的汇编代码是直接从/Fatest.asm中提取的,不是我个人的猜测。 你看到mov DWORD PTR [eax],305419896; 12345678H行?

下面的代码片段certificate了它:

     int main(int argc,char * argv)
     {
        联合{
             char ca [4];
             T t;
         }一个;
         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]) ;
        返回0;
     }

结果是:0x78,0x56,0x34,0x12

  • 在删除了enum T的前向声明之后,在枚举T的定义之后移动了函数foo的定义:结果是OK:

上面的关键指令变成:

mov BYTE PTR [eax],120; 00000078H

最终的结果是:0x78,0x1,0x1,0x1

请注意,该值未被覆盖

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

顺便说一句,毫不奇怪,底层types声明的语法与C#中的相同。 在实践中,我发现当与embedded式系统进行通信时,通过将基本types指定为char来节省3个字节,这是内存有限的。

在我的项目中,我采用了Namespace-Bound Enumeration技术来处理来自传统和第三方组件的enum 。 这里是一个例子:

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.h中:

 #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 (here:main.cc)的文件需要包含enum.h

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

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

在澄清答案:如果你只使用内部的enum ,为什么不在课堂内声明为private

您可以定义一个枚举来将该types元素的可能值限制为有限集合。 这个限制是在编译时执行的。

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

虽然编译器担心枚举types的大小,但是当您转发声明时,枚举的意图会丢失。