【问题标题】:how to define an extensible C++ enum system如何定义可扩展的 C++ 枚举系统
【发布时间】:2012-08-25 02:21:36
【问题描述】:

我在枚举项目中遇到了问题。 在 EventDef.h 中,

enum EventDef {
    EVT1 = 0,
    EVT2,
    EVT3,
    EVT_NUM,
}

这样,我可以在另一个头文件UIEventDef.h中扩展EventDef系统

#include "EventDef.h"
enum UIEventDef {
    UIEVT1 = EVT_NUM,
    UIEVT2,
    UIEVT3,
}

但是,有一个限制,我不能以同样的方式在 NetEvent.h 中执行此操作。

#include "EventDef.h"
enum NetEventDef {
    NETEVT1 = EVT_NUM,
    NETEVT2,   //wrong: this will have the same value as UIEVT2
    NETEVT3,  
}

在 C++ 中是否有更好的编译时解决方案,例如可以提供帮助的模板?

【问题讨论】:

  • 具有相同值的问题到底是什么?无论如何,类型不同(UIEventDef 与 NetEventDef),因此您可能不应该比较它们。
  • 顺便说一句,我使用了 COUNTER 宏,但遗憾的是它不起作用:(
  • 到 zxcdw。我有一个 switch-case 事件处理程序,所以我需要它们具有不同的值。是的,我可以给他们分类,但我只是想要一个更好、更灵活的解决方案
  • @rechardchen:你想达到什么目标?枚举的目的是提供 不同的 类型,这样即使相同的值也无关紧要。您基本上是在尝试实现相反的效果:在不同标题中具有不同值的单个范围......您应该说明您想要 解决 的问题,而不是询问如何处理您的 损坏的设计。
  • @rechardchen:提供类别是一个比单个枚举在所有方面都更灵活的解决方案

标签: c++ templates macros enums


【解决方案1】:

可扩展枚举的想法本质上并不是“糟糕的设计”。在其他语言中有它们的历史,即使 c++ 不直接支持它们。有不同种类的可扩展性。

可扩展枚举的用途

  • 错误代码
  • 消息类型
  • 设备标识(OID 是一个分层枚举类系统)

枚举可扩展性示例

  • Objective Modula 2 具有可通过继承等类进行扩展的枚举。
  • The Extensible Enum Pattern in Java,可以用c++实现。
  • Java 枚举是可扩展的,因为额外的数据和方法可以成为枚举的一部分。
  • 在 C++ 中,typeid 运算符本质上是编译器生成的带有附加值的枚举。

您在示例代码中展示的那种可扩展性在单独的 c++ 中没有优雅的实现。事实上,正如你所指出的,它很容易导致问题。

考虑一下您希望如何使用可扩展枚举。也许一组不可变单例对象的集合/映射将满足您的需求。

在 c++ 中使用可扩展枚举的另一种方法是使用代码生成器。每个想要添加到可扩展枚举的编译单元都将 id 记录在自己的单独的 .enum 文件中。在构建时,在编译之前,一个脚本(即 perl、bash、...)会查找所有 .enum 文件,读取它们,为每个 id 分配数值,并写出一个头文件,该文件与其他文件一样包含在内。

【讨论】:

    【解决方案2】:

    为什么要这样声明事件枚举?如果您愿意,按照您描述的方式将它们“链接”起来,您会获得什么?

    我会让它们成为完全独立的枚举。其次,我建议您不要再使用旧式枚举。 c++11 在这里并且在 gcc 中可用。你应该使用枚举类:

    enum class EventDef  : unsigned { Evt1 = 0, Evt2, Evt3, ... LastEvt }
    enum class NetEvtDef : unsigned { NetEvt1 = 0, NetEvt2, NetEvt3, ... NetLastEvt }
    

    如果你要切换,你可以这样做:

    void doSwitch(EventDef evt_def)
    {
      switch(evt_def)
      {
        case EventDef::Evt1
        {
         // Do something;
         break;
        }
        default:
        // Do something;
      };
    }
    
    void doSwitch(NetEvtDef net_def)
    {
      switch(net_def)
      {
        case NetEvtDef::NetEvt1
        {
         // Do something;
         break;
        }
        default:
        // Do something;
      };
    }
    

    通过为 doSwitch 创建一个重载函数,您可以隔离所有枚举类型。将它们放在不同的类别中是一种好处而不是问题。它为您提供了以不同方式处理每种事件枚举类型的灵活性。

    按照您的描述将它们链接在一起不必要地使问题复杂化。

    希望对你有帮助。

    【讨论】:

    • 好,谢谢你让我知道新的 c++11 样式枚举。 :) 它适用于 VS2008 和 xcode4.x 吗?我想我想归档的是objective-C中的类别..
    • 有些情况下您需要可扩展的枚举类。重载切换功能对我来说似乎是一个拐杖。
    【解决方案3】:

    我发现以下是复杂性、功能和类型安全之间的有用折衷。它使用具有默认构造函数的自定义类的全局变量来简化初始化。下面的示例是一组可扩展的错误代码。您可能还想包含在名称空间中(但我通常不会打扰)。

    //
    //  ErrorCodes.h
    //  ExtendableEnum
    //
    //  Created by Howard Lovatt on 10/01/2014.
    //
    
    #ifndef ErrorCodes_h
    #define ErrorCodes_h
    
    #include <string>
    
    class ErrorCodes {
    public:
        static int nextValue_;
        explicit ErrorCodes(std::string const name) : value_{nextValue_++}, name_{name} {}
        ErrorCodes() : ErrorCodes(std::to_string(nextValue_)) {}
        int value() const { return value_; }
        std::string name() const { return name_; }
    private:
        int const value_;
        std::string const name_;
        ErrorCodes(const ErrorCodes &);
        void operator=(const ErrorCodes &);
    };
    
    int ErrorCodes::nextValue_ = 0; // Weird syntax, does not declare a variable but rather initialises an existing one!
    ErrorCodes first;
    ErrorCodes second;
    // ...
    
    #endif
    
    
    //
    //  ExtraErrorCodes.h
    //  ExtendableEnum
    //
    //  Created by Howard Lovatt on 10/01/2014.
    //
    
    #ifndef ExtraErrorCodes_h
    #define ExtraErrorCodes_h
    
    #include "ErrorCodes.h"
    
    ErrorCodes extra{"Extra"};
    
    #endif
    
    
    //
    //  ExtraExtraExtraCodes.h
    //  ExtendableEnum
    //
    //  Created by Howard Lovatt on 10/01/2014.
    //
    
    #ifndef ExtendableEnum_ExtraExtraCodes_h
    #define ExtendableEnum_ExtraExtraCodes_h
    
    #include "ErrorCodes.h"
    
    ErrorCodes extraExtra{"ExtraExtra"};
    
    #endif
    
    
    //
    //  main.cpp
    //  ExtendableEnum
    //
    //  Created by Howard Lovatt on 10/01/2014.
    //
    
    #include <iostream>
    #include "ErrorCodes.h"
    #include "ExtraErrorCodes.h"
    #include "ExtraExtraErrorCodes.h"
    
    // Need even more error codes
    ErrorCodes const localExtra;
    
    int main(int const notUsed, const char *const notUsed2[]) {
        std::cout << first.name() << " = " << first.value() << std::endl;
        std::cout << second.name() << " = " << second.value() << std::endl;
        std::cout << extra.name() << " = " << extra.value() << std::endl;
        std::cout << extraExtra.name() << " = " << extraExtra.value() << std::endl;
        std::cout << localExtra.name() << " = " << localExtra.value() << std::endl;
        return 0;
    }
    

    输出是:

    0 = 0
    1 = 1
    Extra = 2
    ExtraExtra = 3
    4 = 4
    

    如果您有多个编译单元,那么您需要使用单例模式的变体:

    class ECs {
    public:
        static ErrorCode & first() {
            static ErrorCode instance;
            return instance;
        }
        static ErrorCode & second() {
            static ErrorCode instance;
            return instance;
        }
    private:
        ECs(ECs const&);
        void operator=(ECs const&);
    };
    

    【讨论】:

    • 补充说,如果你有多个编译单元,那么你必须像往常一样使用静态访问方法来克服初始化顺序和多个定义。
    【解决方案4】:

    我们可以在 C++ 中构造一个可扩展的“枚举”,如下所示:

    struct Last {};
    
    struct D 
    {
        using Next = Last;
        static const char* name = “D”;
    };
    
    struct C
    {
        using Next = D;
        static const char* name = “C”;
    };
    
    struct B 
    {
        using Next = C;
        static const char* name = “B”;
    };
    
    using First = B;
    

    我们可以使用这些构造遍历上述内容:

    void Process(const B&)
    {
        // do something specific for B
        cout << “Call me Ishmael” << endl;
    }
    
    template <class T>
    void Process(const T&)
    {
        // do something generic
        cout << “Call me “ << T::name << endl;
    }
    
    template <class T>
    struct IterateThru
    {
       static void iterate() 
       {
           Process(T());
           IterateThru<T::Next>::iterate();
       }
    };
    
    template <>
    struct IterateThru<Last>
    {
        static void iterate()
       {
           // end iteration
       }
    };
    

    遍历“枚举”:

    IterateThru<First>::iterate();
    

    扩展“枚举”:

    struct A
    {
        using Next = B;
        static const char* name = “A”;
    }:
    
    using First = A:
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      • 1970-01-01
      • 2016-02-14
      相关资源
      最近更新 更多