首页 文章

转发声明c中的枚举

提问于
浏览
239

我正在尝试做类似以下的事情:

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内部声明枚举,并将构建的库文件和 Headers 分发给人 .

至于编译器 - 它是GCC .

18 回答

  • 13

    只是注意到原因实际上是在向前声明后尚未知道枚举的大小 . 好吧,你使用结构的前向声明来传递指针或从前面声明的结构定义本身引用的地方引用一个对象 .

    向前声明一个枚举不会太有用,因为人们希望能够传递enum by-value . 你甚至没有指向它的指针,因为我最近被告知一些平台使用不同大小的指针而不是int或long . 所以这一切都取决于枚举的内容 .

    目前的C标准明确禁止做类似的事情

    enum X;
    

    (在 7.1.5.3/1 ) . 但明年的下一个C标准允许以下内容,这使我确信问题实际上与底层类型有关:

    enum X : int;
    

    它被称为"opaque"枚举声明 . 您甚至可以在以下代码中使用X by value . 并且稍后可以在枚举的后续重新定义中定义其枚举器 . 请参阅当前工作草案中的 7.2 .

  • 7

    在回答澄清时:如果仅在内部使用 enum ,为什么不在类中声明它为 private

  • 1

    我这样做:

    [在公共 Headers 中]

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

    [在内部 Headers 中]

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

    通过添加FORCE_32BIT,我们确保Econtent编译为long,因此它可以与E互换 .

  • 5

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

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

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

  • 2

    似乎它无法在海湾合作委员会中向前宣布!

    有趣的讨论here

  • 199

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

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

  • 1

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

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

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

  • 2

    有一些不同意见,因为这有点受到冲击(有点),所以这里有一些来自标准的相关位 . 研究表明,该标准并未真正定义前向声明,也没有明确规定枚举可以或不可以向前声明 .

    首先,来自dcl.enum,第7.2节:

    枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值 . 它是实现定义的,其中整数类型用作枚举的基础类型,除了基础类型不应大于int,除非枚举器的值不能适合int或unsigned int . 如果枚举器列表为空,基础类型就好像枚举具有值为0的单个枚举器 . 应用于枚举类型,枚举类型的对象或枚举器的sizeof()的值是sizeof的值()应用于基础类型 .

    因此枚举的基础类型是实现定义的,只有一个小的限制 .

    接下来我们转到关于“不完整类型”(3.9)的部分,这与我们在前向声明上的任何标准一样接近:

    已声明但未定义的类,或未知大小或元素类型不完整的数组,是未完全定义的对象类型 . 类类型(例如“类X”)可能在翻译单元中的某个点处不完整,稍后会完成;类型“class X”在两个点都是相同的类型 . 声明的数组对象类型可能是一个不完整类类型的数组,因此不完整;如果稍后在翻译单元中完成类类型,则数组类型完成;这两个点的数组类型是相同的类型 . 声明的数组对象类型可能是一个未知大小的数组,因此在翻译单元的某一点不完整,稍后会完成;这两个点的数组类型(“T的未知边界数组”和“N T数组”)是不同的类型 . 指向未知大小数组或由typedef声明定义为未知大小数组的类型的指针类型无法完成 .

    所以,标准几乎列出了可以向前声明的类型 . Enum不在那里,因此编译器作者通常认为由于其基础类型的可变大小而向前声明为标准不允许 .

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

  • 0

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

    #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

  • 0

    枚举无法向前声明的原因是,在不知道值的情况下,编译器无法知道枚举变量所需的存储 . C编译器允许根据包含指定的所有值所需的大小来指定实际存储空间 . 如果所有可见的是前向声明,则翻译单元无法知道将选择哪种存储大小 - 它可以是char或int,或其他 .


    从ISO C标准的第7.2.5节:

    枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值 . 它是实现定义的,其中整数类型用作枚举的基础类型,除了基础类型不应大于int,除非枚举器的值不能适合int或unsigned int . 如果枚举器列表为空,则基础类型就好像枚举具有值为0的单个枚举器 . 应用于枚举类型,枚举类型的对象或枚举器的sizeof()的值是sizeof()应用于基础类型 .

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

    更新:在C 0X中,已经提出并接受了用于向前声明枚举类型的语法 . 您可以在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf查看该提案

  • 2

    在C 0x中也可以进行枚举的前向声明 . 以前,枚举类型无法向前声明的原因是因为枚举的大小取决于其内容 . 只要枚举的大小由应用程序指定,就可以向前声明:

    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.
    
  • 180

    如果你真的不希望你的枚举出现在你的头文件中并确保它只被私有方法使用,那么一个解决方案可以采用pimpl原则 .

    这是一种技术,通过声明:确保隐藏 Headers 中的类内部结构:

    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的优点,一个是它将类头与其实现分离,在更改一个类实现时无需重新编译其他类 . 另一个是加快编译时间,因为 Headers 非常简单 .

    但是使用起来很痛苦,所以你应该问自己,如果只是在 Headers 中声明你的枚举是私有的那么麻烦 .

  • 67

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

    1 - 使用int而不是枚举:在CPP文件中的匿名命名空间中声明你的int(不在 Headers 中):

    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 .

  • 32

    鉴于最近的发展,我在这里添加了一个最新的答案 .

    只要您同时声明其存储类型,就可以在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支持这一点;在早期版本中它有一些我尚未想到的非标准支持 - 我发现一些简单的前向声明是合法的,但是YMMV .

  • 4

    在我的项目中,我采用了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 (此处为:main.cc)的文件才需要包含 enum.h .

  • 1

    在C语言中声明事物非常有用,因为它是dramatically speeds up compilation time . 你可以在C中转发声明几件事,包括: structclassfunction 等......

    但是你可以在C中转发声明 enum 吗?

    不,你不能 .

    但为什么不允许呢?如果允许,您可以在头文件中定义 enum 类型,并在源文件中定义 enum 值 . 听起来应该被允许对吗?

    错误 .

    在C中, enum 没有默认类型,就像在C#(int)中一样 . 在C中,您的 enum 类型将由编译器确定为适合 enum 的值范围的任何类型 .

    那是什么意思?

    这意味着在定义了 enum 的所有值之前,无法完全确定 enum 的基础类型 . 您无法将 enum 的声明和定义分开 . 因此,您无法在C中转发声明 enum .

    ISO C标准S7.2.5:

    枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值 . 它是实现定义的,其中整数类型用作枚举的基础类型,除了基础类型不应大于int,除非枚举器的值不能适合int或unsigned int . 如果枚举器列表为空,则基础类型就好像枚举具有值为0的单个枚举器 . 应用于枚举类型,枚举类型的对象或枚举器的sizeof()的值是sizeof()应用于基础类型 .

    您可以使用 sizeof 运算符确定C中枚举类型的大小 . 枚举类型的大小是其基础类型的大小 . 通过这种方式,您可以猜出编译器用于 enum 的类型 .

    如果您明确指定 enum 的类型,请执行以下操作:

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

    那么你可以转发声明你的 enum 吗?

    不,但为什么不呢?

    指定 enum 的类型实际上不是当前C标准的一部分 . 它是VC扩展 . 它虽然是C 0x的一部分 .

    Source

  • 0

    您可以定义枚举以将类型元素的可能值限制为有限集 . 此限制将在编译时强制执行 .

    当前面声明您稍后将使用“有限集”这一事实时,不会添加任何值:后续代码需要知道可能的值才能从中受益 .

    尽管编译器关注枚举类型的大小,但是当您转发声明它时,枚举的意图会丢失 .

  • 1

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

    • 以下代码编译好了 .
    typedef int myint;
        enum T ;
        void foo(T * tp )
        {
            * tp = (T)0x12345678;
        }
        enum T : char
        {
            A
        };
    

    但得到/ W4的警告(/ W3没有招致此警告)

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

    • VC(Microsoft(R)32位C / C优化编译器版本15.00.30729.01 for 80x86)在上述情况下看起来有些错误:

    • 看到enum 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.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;
        }
    

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

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

    该以上关键指令变为:

    mov BYTE PTR [eax],120; 00000078H

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

    请注意,该值不会被覆盖

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

    顺便说一下,基础类型声明的语法与C#中的语法相同 . 在实践中,我发现通过在与嵌入式系统通信时将底层类型指定为char来保存3个字节是值得的,这是内存有限的 .

相关问题