【问题标题】:Are enums the canonical way to implement bit flags?枚举是实现位标志的规范方法吗?
【发布时间】:2014-08-06 17:38:06
【问题描述】:

目前我在一个小游戏实验中使用枚举来表示一个状态。我这样声明它们:

namespace State {
  enum Value {
    MoveUp = 1 << 0, // 00001 == 1
    MoveDown = 1 << 1, // 00010 == 2
    MoveLeft = 1 << 2, // 00100 == 4
    MoveRight = 1 << 3, // 01000 == 8
    Still = 1 << 4, // 10000 == 16
    Jump = 1 << 5
  };
}

这样我就可以这样使用它们了:

State::Value state = State::Value(0);
state = State::Value(state | State::MoveUp);
if (mState & State::MoveUp)
  movement.y -= mPlayerSpeed;

但我想知道这是否是实现位标志的正确方法。位标志没有特殊的容器吗?我听说过std::bitset,我应该使用它吗?你知道更有效的方法吗?
我做得对吗?


我忘了指出我正在重载枚举的基本运算符:
inline State::Value operator|(State::Value a, State::Value b)
{ return static_cast<State::Value>(static_cast<int>(a) | static_cast<int>(b)); }

inline State::Value operator&(State::Value a, State::Value b)
{ return static_cast<State::Value>(static_cast<int>(a) & static_cast<int>(b)); }


inline State::Value& operator|=(State::Value& a, State::Value b)
{ return (State::Value&)((int&)a |= (int)b); }

我不得不为 |= 使用 C 风格的演员表,它不适用于 static_cast - 知道为什么吗?

【问题讨论】:

  • 有这个,但我不确定这是否是“规范”的做事方式...stackoverflow.com/a/439004/541686
  • 对我来说太重了。我想要 lightest 方法,如有必要,仅使用纯 C++ 或标准库。没有升压。我的目标是尽量减少内存和 CPU 的使用,因为我正在制作游戏,而一毫秒的延迟将意味着 fps 数量巨大下降。
  • 我可以理解“没有 Boost”,但我无法理解 run 时对内存或 CPU 使用率的担忧,因为这些都是编译时常量,而不是运行时值...
  • 我相信唯一需要的处理是如果你想获取与枚举关联的字符串,无论如何你不应该在游戏引擎中经常这样做。否则枚举在类内是公开的,它就像任何其他枚举一样......
  • @Mehrdad:它看起来像一个带有顺序值的直接枚举。可以用来指定位标志枚举吗?

标签: c++ enums bit-manipulation bitflags std-bitset


【解决方案1】:

STL 包含std::bitset,您可以将其用于这种情况。

这里的代码足以说明这个概念:

#include <iostream>
#include <bitset>

class State{
public:
    //Observer
    std::string ToString() const { return state_.to_string();};
    //Getters
    bool MoveUp()    const{ return state_[0];}; 
    bool MoveDown()  const{ return state_[1];}; 
    bool MoveLeft()  const{ return state_[2];}; 
    bool MoveRight() const{ return state_[3];}; 
    bool Still()     const{ return state_[4];}; 
    bool Jump()      const{ return state_[5];}; 
    //Setters
    void MoveUp(bool on)    {state_[0] = on;}
    void MoveDown(bool on)  {state_[1] = on;}
    void MoveLeft(bool on)  {state_[2] = on;}
    void MoveRight(bool on) {state_[3] = on;}
    void Still(bool on)     {state_[4] = on;}
    void Jump(bool on)      {state_[5] = on;}
private:
    std::bitset<6> state_;
};


int main() {
    State s;
    auto report = [&s](std::string const& msg){
        std::cout<<msg<<" "<<s.ToString()<<std::endl;
    };
    report("initial value");
    s.MoveUp(true);
    report("move up set");
    s.MoveDown(true);
    report("move down set");
    s.MoveLeft(true);
    report("move left set");
    s.MoveRight(true);
    report("move right set");
    s.Still(true);
    report("still set");
    s.Jump(true);
    report("jump set");
    return 0;
}

它正在运行:http://ideone.com/XLsj4f

有趣的是,您可以免费获得 std::hash 支持,这通常是您在各种数据结构中使用状态时需要的东西之一。

编辑: std::bitset 有一个限制,那就是您需要在编译时知道 bitset 中的最大位数。但是,无论如何,枚举的情况都是一样的。

但是,如果您在编译时不知道您的 bitset 的大小,您可以使用boost::dynamic_bitset,根据to this paper(参见第 5 页)实际上非常快。最后,根据Herb Sutter,std::bitset 旨在用于您通常想要使用 std::vector 的情况。

也就是说,现实世界的测试确实无可替代。所以如果你真的想知道,profile。这将为您提供您关心的上下文的性能数据。

我还应该提到 std::bitset 具有 enum 没有的优势 - 您可以使用的位数没有上限。所以 std::bitset 是完全有效的。

【讨论】:

  • 感谢您解释如何将位集用于位标志!我很想看看这个与位枚举的基准。
【解决方案2】:

我相信你的方法是正确的(除了几件事):
1.可以显式指定底层类型以节省内存;
2. 不能使用未指定的枚举值。

namespace State {
  enum Value : char {
    None      = 0,
    MoveUp    = 1 << 0, // 00001 == 1
    MoveDown  = 1 << 1, // 00010 == 2
    MoveLeft  = 1 << 2, // 00100 == 4
    MoveRight = 1 << 3, // 01000 == 8
    Still     = 1 << 4, // 10000 == 16
    Jump      = 1 << 5
  };
}

和:

State::Value state = State::Value::None;
state = State::Value(state | State::MoveUp);
if (mState & State::MoveUp) {
  movement.y -= mPlayerSpeed;
}

关于重载:

inline State::Value& operator|=(State::Value& a, State::Value b) {
    return a = static_cast<State::Value> (a | b);
}

既然你使用 C++11,你应该使用 constexpr 一切皆有可能:

inline constexpr State::Value operator|(State::Value a, State::Value b) {
    return a = static_cast<State::Value> (a | b);
}

inline constexpr State::Value operator&(State::Value a, State::Value b) {
    return a = static_cast<State::Value> (a & b);
}

【讨论】:

  • 为什么是mState &amp;= ~State::MoveUp;?它究竟是做什么的?为什么让我的枚举继承自char?关于|= 运算符:我将编辑我的问题,我无法使用它。
  • @filsmick:enum Value : char 语法并不是真正的继承,它是 C++11 语法告诉编译器您希望枚举所基于的整数类型是 @987654329 @。在以前的 C++ 版本中,不允许您指定。在这种情况下,这意味着您的sizeof(Value) == sizeof(char)
  • @filsmick:sizeof(char)根据定义是 1,但这并不一定意味着它是 8 位。在某些平台上可能会更多。
  • @filsmick,我想演示如何管理内存的方法。显然你应该使用更合适的类型int16_tint32_tint64_t
  • @filsmick:“通常”不正确。 sizeof(char) 始终为 1,无论它使用多少位。
【解决方案3】:

说实话,我不认为他们有一个一致的模式

只需将std::ios_base::openmodestd::regex_constants::syntax_option_type 视为在标准库中构建它的两种完全不同的方式——一种使用结构,另一种使用整个命名空间。两者都是枚举,但结构不同。
检查您的标准库实现以查看上述两个实现的详细信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-13
    • 2016-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多