【问题标题】:enum class as array index枚举类作为数组索引
【发布时间】:2016-06-26 11:35:20
【问题描述】:

我做了一个枚举:

enum class KeyPressSurfaces {
    KEY_PRESS_SURFACE_DEFAULT,
    KEY_PRESS_SURFACE_UP,
    KEY_PRESS_SURFACE_DOWN,
    KEY_PRESS_SURFACE_LEFT,
    KEY_PRESS_SURFACE_RIGHT,
    KEY_PRESS_SURFACE_TOTAL
};

后来我尝试在下面键入时定义一个数组,但我收到了错误,size of array 'KEY_PRESS_SURFACES' has non-integral type 'KeyPressSurfaces'

SDL_Surface*KEY_PRESS_SURFACES[KeyPressSurfaces::KEY_PRESS_SURFACE_TOTAL];

我理解错误很好,但我不知道将KeyPressSurfaces 移到哪里来限定枚举中的常量。

我也意识到我可以只使用enum 而不是enum class,但我觉得这应该可行,我想学习如何做到这一点。

有什么回应/建议吗?

【问题讨论】:

  • 枚举是一个编译时间常数。
  • @user463035818 当您在枚举上添加更多项目时,KEY_PRESS_SURFACE_TOTAL 会自动调整。实际上,将枚举用于数组大小是一种很好的技术。当它是一个常数时,您必须编辑与该数组大小相关的代码中的所有部分,特别是在涉及数组大小的某些计算中。
  • 我很惊讶没有其他人提到这一点:鉴于enum classes 是作用域的,我认为它们的一半是为了避免你不得不重复像@987654330 这样的丑陋前缀@ 在每个枚举器上。您不再需要保护全局命名空间。认为你这样做,现在你必须写它两次...KeyPressSurfaces::KEY_PRESS_SURFACE_DEFAULTYuk!去掉前缀,去掉ALL_CAPS,因为这里没有宏,去掉IMO不必要且最好保留给集合实例的复数,然后写KeyPressSurface::default。好多了。
  • @underscore_d +1 除了KeyPressSurface::default 之外的所有内容,它根本无法编译。使用 Google 指南,您将改为编写 KeyPressSurface::kDefault (google.github.io/styleguide/cppguide.html#Enumerator_Names) 之类的内容
  • 在将值用作数组索引时,使用范围枚举而不是无范围枚举的充分理由是什么?如果是关于范围的,可以将无范围的枚举放入命名空间。

标签: c++ arrays enums sdl


【解决方案1】:

作用域enums (enum class) 不能隐式转换为整数。你需要使用static_cast:

SDL_Surface*KEY_PRESS_SURFACES[static_cast<int>(KeyPressSurfaces::KEY_PRESS_SURFACE_TOTAL)];

【讨论】:

  • 这是对的,但这也是丑陋的,因为这些小块的东西加起来让 c++ 这些天变得不受欢迎了
【解决方案2】:

您可以使用模板函数将您的enum 转换为int,您将获得更具可读性的代码:

#include <iostream>
#include <string>
#include <typeinfo>

using namespace std;

enum class KeyPressSurfaces: int {
    KEY_PRESS_SURFACE_DEFAULT,
    KEY_PRESS_SURFACE_UP,
    KEY_PRESS_SURFACE_DOWN,
    KEY_PRESS_SURFACE_LEFT,
    KEY_PRESS_SURFACE_RIGHT,
    KEY_PRESS_SURFACE_TOTAL
};

template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) {
    return static_cast<typename std::underlying_type<E>::type>(e);
}


int main() {
    KeyPressSurfaces val = KeyPressSurfaces::KEY_PRESS_SURFACE_UP;
    int valInt = to_underlying(val);
    std::cout << valInt << std::endl;
    return 0;
}

我找到to_underlying函数here

【讨论】:

    【解决方案3】:

    删除 class 关键字或显式转换为整数类型。

    【讨论】:

      【解决方案4】:

      在其他响应的基础上,另一种选择是包装 c 样式数组的简单模板类。在下面的 EnumArray 示例中,任何带有kMaxValueenum class 都可以用作索引。

      在我看来,提高可读性值得引入模板。

      template <class IndexType, class ValueType>
      class EnumArray {
       public:  
        ValueType& operator[](IndexType i) { 
          return array_[static_cast<int>(i)];
        }
      
        const ValueType& operator[](IndexType i) const {
          return array_[static_cast<int>(i)];
        }
      
        int size() const { return size_; }
      
       private:
        ValueType array_[static_cast<int>(IndexType::kMaxValue) + 1];
      
        int size_ = static_cast<int>(IndexType::kMaxValue) + 1;
      }; 
      
      enum class KeyPressSurfaces {
          KEY_PRESS_SURFACE_DEFAULT,
          KEY_PRESS_SURFACE_UP,
          KEY_PRESS_SURFACE_DOWN,
          KEY_PRESS_SURFACE_LEFT,
          KEY_PRESS_SURFACE_RIGHT,
          KEY_PRESS_SURFACE_TOTAL,
          kMaxValue = KEY_PRESS_SURFACE_TOTAL
      };
      
      int main() {
          EnumArray<KeyPressSurfaces, int> array;
          array[KeyPressSurfaces::KEY_PRESS_SURFACE_DEFAULT] = 5;
          std::cout << array[KeyPressSurfaces::KEY_PRESS_SURFACE_DEFAULT] << std::endl;
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        您可以使用命名空间和匿名枚举。所以你可以去掉这些丑陋的前缀,使用枚举项作为索引。

        namespace KeyPressSurfaces
        {
            enum
            {
                DEFAULT = 0,
                UP,
                DOWN,
                LEFT,
                RIGHT,
                TOTAL
            };
        }
        
        SDL_Surface* KEY_PRESS_SURFACES[KeyPressSurfaces::TOTAL];
        

        【讨论】:

          【解决方案6】:

          你可以在数组上工作:

          /** \brief It's either this or removing the "class" from "enum class" */
          template <class T, std::size_t N>
          struct EnumClassArray : std::array<T, N>
          {
              template <typename I>
              T& operator[](const I& i) { return std::array<T, N>::operator[](static_cast<std::underlying_type<I>::type>(i)); }
              template <typename I>
              const T& operator[](const I& i) const { return std::array<T, N>::operator[](static_cast<std::underlying_type<I>::type>(i)); }
          };
          

          【讨论】:

            【解决方案7】:

            除了当前接受的答案之外,您还可以编写一个函数来获取对表面的引用:

            enum class KeyPressSurface
            {
                DEFAULT,
                UP,
                DOWN,
                LEFT,
                RIGHT,
                TOTAL
            };
            // This is using static_cast like the accepted answer
            std::array<SDL_Surface *, static_cast<int>(KeyPressSurface::TOTAL)> keyPressSurfaces;
            
            // Function to get a reference to a surface
            SDL_Surface *&getKeyPressSurface(KeyPressSurface surface)
            {
                return keyPressSurfaces[static_cast<int>(surface)];
            }
            
            

            现在您可以使用枚举类干净利落地获得一个表面:

            // assignment
            getKeyPressSurface(KeyPressSurface::UP) = SDL_LoadBMP("lamp.bmp");
            // or get a value
            SDL_Surface *currentSurface = getKeyPressSurface(KeyPressSurface::RIGHT);
            

            【讨论】:

              【解决方案8】:

              使用结构成员而不是枚举。

              struct KeyPressSurfaces {
                  static constexpr int KEY_PRESS_SURFACE_DEFAULT = 0;
                  static constexpr int KEY_PRESS_SURFACE_UP= 1;
                  static constexpr int KEY_PRESS_SURFACE_DOWN = 2;
                  static constexpr int KEY_PRESS_SURFACE_LEFT = 3;
                  static constexpr int KEY_PRESS_SURFACE_RIGHT = 4;
                  static constexpr int KEY_PRESS_SURFACE_TOTAL = 5;
              };
              

              或者,将它们放在具有相同逻辑的命名空间中,您可以从 using namespace 中受益。

              namespace KeyPressSurfaces {
                  constexpr int KEY_PRESS_SURFACE_DEFAULT = 0;
                  constexpr int KEY_PRESS_SURFACE_UP= 1;
                  constexpr int KEY_PRESS_SURFACE_DOWN = 2;
                  constexpr int KEY_PRESS_SURFACE_LEFT = 3;
                  constexpr int KEY_PRESS_SURFACE_RIGHT = 4;
                  constexpr int KEY_PRESS_SURFACE_TOTAL = 5;
              }
              
              SDL_Surface* KEY_PRESS_SURFACES[KeyPressSurfaces::KEY_PRESS_SURFACE_TOTAL];
              

              【讨论】:

                【解决方案9】:

                或者,您可以将array 替换为map,这也意味着您可以摆脱KEY_PRESS_SURFACE_TOTAL

                enum class KeyPressSurfaces {
                    KEY_PRESS_SURFACE_DEFAULT,
                    KEY_PRESS_SURFACE_UP,
                    KEY_PRESS_SURFACE_DOWN,
                    KEY_PRESS_SURFACE_LEFT,
                    KEY_PRESS_SURFACE_RIGHT
                };
                
                std::map<KeyPressSurfaces, SDL_Surface*> KEY_PRESS_SURFACES;
                

                【讨论】:

                • 这并不是问题的通用解决方案,因为 std::map 会比使用数组慢。
                • “过早的优化是万恶之源。” - 问题中没有任何内容表明速度是一个问题,编译器可能足够聪明来处理这个问题,我发现这是一个更干净的解决方案。
                • 我很确定没有编译器(过去、现在或将来)会将 std::map 转换为数组。写慢代码因为你不想过早地优化和写慢代码因为你不知道你正在使用的结构很慢,这两者之间也有区别。我碰巧自己现在正在编写一个程序(一个国际象棋引擎),使用 std::map 对性能的影响将是非常残酷的。
                • @parsley72 引用并不总是准确的。通常由过早优化引起的错误是当您的优化目标是速度或内存使用并且您没有考虑程序中的其他内容时。还有其他类型的优化,例如优化可维护性和灵活性,这将在很大程度上减少错误,并且从长远来看对业务有好处。虽然一开始生产可能会很慢,但这是值得的。
                • @AngelM1981 与我们可能争论的其他一切不同,除了宏,尤其是变量名(还有一些传统将它用于枚举器名称,但这仍然是糟糕的 IMO)。
                猜你喜欢
                • 2010-09-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-11-02
                • 2019-11-17
                • 1970-01-01
                • 2014-10-26
                • 2022-08-15
                相关资源
                最近更新 更多