在C ++中向前声明一个枚举

时间:2020-03-05 18:55:41  来源:igfitidea点击:

我正在尝试执行以下操作:

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内部声明该枚举,然后将构建的库文件和头分发给其他人。

至于编译器,它是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中,已经提出并接受了用于声明枚举类型的语法。我们可以在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf上看到该提案。

回答

我对问题的解决方案是:

1使用int代替枚举:在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:创建具有有限const实例化的完整类,就像用Java一样。转发声明该类,然后在CPP文件中定义它,并仅实例化枚举式值。我在C ++中做了类似的事情,结果却不尽如人意,因为它需要一些代码来模拟枚举(复制构造,运算符=等)。

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

我的猜测是解决方案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有很多优点,其中之一是它可以将类头与其实现分离,而在更改一个类的实现时无需重新编译其他类。另一个是,因为标头是如此简单,所以可以加快编译时间。

但是使用起来很痛苦,因此我们应该真正问自己,是否只是在标头中将枚举声明为私有会带来很多麻烦。

回答

我们定义一个枚举,以将类型的元素的可能值限制为一个有限的集合。此限制将在编译时强制执行。

在向前声明我们稍后将使用"有限集"这一事实时,它不会添加任何值:后续代码需要知道可能的值,以便从中受益。

尽管编译器担心枚举类型的大小,但是当我们向前声明枚举时,枚举的意图会丢失。

回答

自从出现这种情况以来,存在一些异议(因此),因此这是该标准中的一些相关内容。研究表明,该标准并未真正定义前向声明,也未明确声明枚举可以或者不能被前向声明。

首先,来自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.

因此,该标准几乎列出了可以向前声明的类型。枚举不存在,因此编译器作者通常将前向声明视为该标准所不允许的,因为其基础类型的大小可变。

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

回答

只是指出原因实际上是在向前声明后仍不知道枚举的大小。好吧,我们可以使用结构的前向声明来传递指针或者从前向声明的结构定义本身中引用的位置引用对象。

向前声明枚举不会太有用,因为人们希望能够传递枚举值。我们甚至都没有指向它的指针,因为最近我被告知某些平台对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不会引发此警告)

警告C4480:使用了非标准扩展名:为枚举" T"指定基础类型

?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.asm中提取的,不是我个人的猜测。
你看到了吗
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;
    }

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

  • 在删除枚举T的前向声明并将函数foo的定义移到枚举T的定义之后:结果正常:

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

上面的关键说明变为:

mov BYTE PTR [eax],120; 00000078H

最终结果是:
0x78、0x1、0x1、0x1

请注意,该值未被覆盖

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

回答

顺便说一句,不足为奇的是,用于声明基础类型的语法与C#中的语法相同。在实践中,我发现值得与嵌入式系统对话时将底层类型指定为char来节省3个字节,这是受内存限制的。

forward.h:

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

枚举

// 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(在这里是main.cc)的文件才需要包含enum.h`。

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 ++ 0x中也可以向前声明枚举。以前,无法向前声明枚举类型的原因是因为枚举的大小取决于其内容。只要应用程序指定了枚举的大小,就可以向前声明:

在C ++中进行前向声明非常有用,因为它可以大大加快编译时间。我们可以在C ++中向前声明一些东西,包括:structclassfunction等。

但是我们可以在C ++中向前声明一个"枚举"吗?

不,你不能。

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

错误的。

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

那是什么意思?

这意味着,直到我们定义了"枚举"的所有值后,才能完全确定"枚举"的基础类型。我们无法区分哪个人的enum的声明和定义。因此,我们不能在C ++中向前声明一个"枚举"。

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.

ISO C ++标准S7.2.5:

我们可以使用sizeof运算符来确定C ++中枚举类型的大小。枚举类型的大小是其基础类型的大小。这样,我们可以猜测enum使用的编译器类型。

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

如果我们像这样明确地指定enum的类型,该怎么办:

然后可以转发声明"枚举"吗?

不,为什么不呢?

指定"枚举"的类型实际上并不是当前C ++标准的一部分。它是VC ++扩展。但是它将成为C ++ 0x的一部分。

段落数量不匹配