枚举类作为数组索引

27

我定义了一个枚举:

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,但我觉得这应该能够工作,并且我想学习如何做到这一点。

有什么回复或建议吗?


13
枚举是一个编译时常量。 - parsley72
1
当您在枚举中添加更多项时,KEY_PRESS_SURFACE_TOTAL 会自动调整。实际上,使用枚举来确定数组大小是一种很好的技巧。当它是一个常数时,您必须编辑与该数组大小相关的所有代码部分,特别是在某些涉及数组大小的计算中。 - acegs
10
我很惊讶没人提到这个:鉴于“枚举类”是有作用域的,我本来以为它们的一半目的是避免你在每个枚举器上重复使用丑陋的前缀,如“KEY_PRESS_SURFACE_”。你不再需要保护全局命名空间。如果你认为需要,现在你必须写两次...KeyPressSurfaces::KEY_PRESS_SURFACE_DEFAULT 太糟糕了!只需放弃前缀,也放弃大写字母,因为这里没有宏,放弃名词复数,因为我认为它是不必要的,最好为集合实例保留,然后写成 KeyPressSurface::default。好多了。 - underscore_d
2
@underscore_d 对于除了 KeyPressSurface::default 之外的所有内容都加1,但是它将无法编译。根据 Google 的指南,您应该编写类似于 KeyPressSurface::kDefault 的内容(https://google.github.io/styleguide/cppguide.html#Enumerator_Names)。 - Andreas Magnusson
1
在将枚举值用作数组索引时,使用范围枚举而不是非限定作用域枚举的好理由是什么?如果涉及作用域,则可以将非限定作用域枚举放入命名空间中。 - Roland Sarrazin
显示剩余3条评论
9个回答

26

作用域枚举(enum class)不能隐式转换为整数类型。您需要使用static_cast进行转换:


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

9
没错,但这也很丑陋,由于这些琐碎的小问题不断积累,导致C++最近变得不那么受欢迎了。 - r0n9
1
在 C++23 中会有一个名为 std::to_underlying 的函数,它将使代码更加清晰易懂。 - Vennor

25

你可以使用模板函数将你的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函数的实现(链接)


值得注意的是,to_underlying 是 C++23 的一个新特性。 - bleater

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)); }
};

很棒。不过在std::underlying_type之前需要加上typename - Chris

4
你可以使用命名空间和匿名枚举。这样,你就可以摆脱这些丑陋的前缀,并将枚举项用作索引。
namespace KeyPressSurfaces
{
    enum
    {
        DEFAULT = 0,
        UP,
        DOWN,
        LEFT,
        RIGHT,
        TOTAL
    };
}

SDL_Surface* KEY_PRESS_SURFACES[KeyPressSurfaces::TOTAL];

4

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


4
建立在其他回答的基础上,另一个替代方案是简单的模板类,用于包装C风格数组。使用以下EnumArray示例,可以将任何具有kMaxValue的enum 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;
}

这看起来是一个最好的解决方案之一,既不失优雅又不失品味。 - Kingsley

1

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

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];

0

除了目前被接受的答案,你可以编写一个函数来获取对表面的引用:

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);

-3

或者你可以用一个map替换你的array,这也意味着你可以摆脱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;

2
这不是问题的通用解决方案,因为使用 std::map 会比使用数组慢。 - Kef Schecter
3
“过早优化是万恶之源。”- 问题中没有暗示速度是一个问题,编译器可能足够聪明以处理此问题,我认为这是一种更简洁的解决方案。 - parsley72
20
我很确定无论是过去、现在还是将来,没有任何编译器会将std::map转换成数组。此外,不想过早优化而写出的慢代码和因为不知道所使用的结构体速度较慢而写出的慢代码也是有区别的。我现在正在编写一个程序(一个国际象棋引擎),如果使用std::map的话,性能会受到非常严重的影响。 - Kef Schecter
1
@parsley72 这句话并不总是准确的。过早进行优化导致的Bug往往是因为你的优化目标是速度或内存使用,而你没有考虑到程序中的其他因素。还有其他类型的优化,比如针对可维护性和灵活性的优化,这将大大减少Bug,并且从长远来看对业务也有好处。虽然一开始生产可能会变慢,但这是值得的。 - acegs
1
@AngelM1981 除了我们可能会争论的其他事情之外,使用 ALL_CAPS 表示除宏以外的任何东西,特别是变量名,会导致可读性和风格质量明显降低(虽然在枚举器名称中也有一些传统用法,但我认为这仍然不好)。 - underscore_d
@underscore_d 我在复制问题的代码以使其更明显。 - parsley72

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接