C++ 枚举类可以有方法吗?

Posted

技术标签:

【中文标题】C++ 枚举类可以有方法吗?【英文标题】:Can a C++ enum class have methods? 【发布时间】:2014-02-13 06:41:16 【问题描述】:

我有一个包含两个值的枚举类,我想创建一个接收值的方法 并返回另一个。我还想维护类型安全(这就是我使用枚举类而不是枚举的原因)。

http://www.cplusplus.com/doc/tutorial/other_data_types/ 没有提及任何有关方法的内容 但是,我的印象是任何类型的类都可以有方法。

【问题讨论】:

不,它不能。见here。 @octavian 注意我的回答,请重新考虑您的用例! @πάνταῥεῖ 你是完全正确的,我读过枚举但认为联合,杀死了评论。 @octavian 你甚至要求一个特定的用例,还是你只是想确认c++11enum class/struct的标准限制?跨度> 我想到了一个用途......这是根本问题 【参考方案1】:

不,他们不能。

我可以理解 C++11 中强类型枚举的 enum class 部分似乎暗示您的 enum 也具有 class 特征,但事实并非如此。我有根据的猜测是,关键字的选择受到了我们在 C++11 之前用于获取作用域枚举的模式的启发:

class Foo 
public:
  enum BAR, BAZ;
;

但是,这只是语法。同样,enum class 不是 class

【讨论】:

在##C++ 上,有人告诉我“c++ 的目标是尽可能地让人困惑和对专家友好”。显然这是个玩笑,但你明白了:) union 也不是 John Doe 认为的 class。然而它们可以有成员函数。并且类对于成员函数实际上不是强制性的。使用valuethis 之类的指示符,enum Size Huge, Mega, Apocalypse; bool operator<(X rhs) const return *this < rhs; 之类的东西(这里也允许;),它与其他形式的函数一样有意义。 为什么不是struct Foo enum BAR, BAZ; ;,所以不是enum struct 哦,enum struct 实际上是创建作用域枚举的有效方法!谁知道!【参考方案2】:

虽然“您不能”的答案在技术上是正确的,但我相信您可以使用以下想法实现您正在寻找的行为:

我想你想写这样的东西:

Fruit f = Fruit::Strawberry;
f.IsYellow();

你希望代码看起来像这样:

enum class Fruit : uint8_t

  Apple, 
  Pear,
  Banana,
  Strawberry,

  bool IsYellow()  return this == Banana; 
;

但当然,它不起作用,因为枚举不能有方法(并且'this'在上述上下文中没有任何意义)

但是,如果您使用包含非类枚举和包含该类型值的单个成员变量的普通类的想法,您可以非常接近您想要的语法/行为/类型安全。即:

class Fruit

public:
  enum Value : uint8_t
  
    Apple,
    Pear,
    Banana,
    Strawberry
  ;

  Fruit() = default;
  constexpr Fruit(Value aFruit) : value(aFruit)  

#if Enable switch(fruit) use case:
  // Allow switch and comparisons.
  constexpr operator Value() const  return value; 

  // Prevent usage: if(fruit)
  explicit operator bool() = delete;        
#else
  constexpr bool operator==(Fruit a) const  return value == a.value; 
  constexpr bool operator!=(Fruit a) const  return value != a.value; 
#endif

  constexpr bool IsYellow() const  return value == Banana; 

private:
  Value value;
;

现在你可以写了:

Fruit f = Fruit::Strawberry;
f.IsYellow();

编译器会阻止这样的事情:

Fruit f = 1;  // Compile time error.

您可以轻松添加如下方法:

Fruit f("Apple");

f.ToString();

可以支持。

【讨论】:

不应该也是IsYellow(), operator==, != 标记为constexpr吗? 我收到“错误:在令牌“switch”之前缺少二元运算符“ 这真的很聪明。有没有办法让类中的枚举也成为enum class 能否详细说明一下宏 if-else 部分? 您的先生,这是迄今为止最好的答案。【参考方案3】:

专注于问题的描述而不是标题,可能的答案是

struct LowLevelMouseEvent 
    enum Enum 
        mouse_event_uninitialized = -2000000000, // generate crash if try to use it uninitialized.
        mouse_event_unknown = 0,
        mouse_event_unimplemented,
        mouse_event_unnecessary,
        mouse_event_move,
        mouse_event_left_down,
        mouse_event_left_up,
        mouse_event_right_down,
        mouse_event_right_up,
        mouse_event_middle_down,
        mouse_event_middle_up,
        mouse_event_wheel
    ;
    static const char* ToStr (const type::LowLevelMouseEvent::Enum& event)
    
        switch (event) 
            case mouse_event_unknown:         return "unknown";
            case mouse_event_unimplemented:   return "unimplemented";
            case mouse_event_unnecessary:     return "unnecessary";
            case mouse_event_move:            return "move";
            case mouse_event_left_down:       return "left down";
            case mouse_event_left_up:         return "left up";
            case mouse_event_right_down:      return "right down";
            case mouse_event_right_up:        return "right up";
            case mouse_event_middle_down:     return "middle down";
            case mouse_event_middle_up:       return "middle up";
            case mouse_event_wheel:           return "wheel";
            default:
                Assert (false);
                break;
        
        return "";
    
;

【讨论】:

【参考方案4】:

如other answer 中所述,没有。甚至enum class 也不是一个类。


通常需要拥有enum的方法是因为它不是常规(只是递增)枚举,而是值的按位定义被屏蔽或需要其他位算术运算:

enum class Flags : unsigned char 
    Flag1 = 0x01 , // Bit #0
    Flag2 = 0x02 , // Bit #1
    Flag3 = 0x04 , // Bit #3
    // aso ...


// Sets both lower bits
unsigned char flags = (unsigned char)(Flags::Flag1 | Flags::Flag2);

// Set Flag3
flags |= Flags::Flag3;

// Reset Flag2
flags &= ~Flags::Flag2;

显然,人们考虑封装必要的操作来重新/设置单个/一组位,例如位掩码值甚至位索引驱动的操作对于操作这样一组“标志”很有用。

c++11 struct/class specification 只支持更好地限定枚举值以进行访问。不多不少!

摆脱不能为枚举(类)声明方法的限制的方法是,使用std::bitset(包装类)或bitfield union

unions,这样的位域联合可以有方法(限制见here!)。

我有一个示例,如何将位掩码值(如上所示)转换为相应的位索引,可以在此处与 std::bitset 一起使用:BitIndexConverter.hpp 我发现这对于增强某些基于“标志”决策的算法的可读性非常有用。

【讨论】:

还有更多的用例需要枚举类的方法,例如toString() 和 fromString()。每种(甚至不是)现代主要语言都有这个(例如 C#、Java、Swift),只是没有 C++。 希望下次统一调用语法...open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf【参考方案5】:

有一种相当兼容的能力(§)可以将枚举重构为一个类,而无需重写代码,这意味着您可以有效地做自己的事情要求不要进行过多的编辑。

(§) 正如 ElementW 在评论中指出的那样,依赖于 type_traits 的代码将不起作用,例如不能用auto之类的。可能有一些处理这些东西的方法,但最终是把一个枚举转换成一个类,而颠覆C++总是错误的

enum structenum class 规范是关于范围的,因此不是其中的一部分。

您的原始枚举是例如'pet'(这仅作为示例!)。

enum pet  
    fish, cat, dog, bird, rabbit, other 
;

(1) 您将其修改为例如 petEnum(以便将其隐藏在您现有的代码中)。

enum petEnum  
    fish, cat, dog, bird, rabbit, other 
;

(2) 在它下面添加一个新的类声明(以原始枚举命名)

class pet 
    private:
        petEnum value;
        pet() 

    public:
        pet(const petEnum& v) : valuev  //not explicit here.
        operator petEnum() const  return value; 
        pet& operator=(petEnum v)  value = v; return *this;
        bool operator==(const petEnum v) const  return value == v; 
        bool operator!=(const petEnum v) const  return value != v; 
 //     operator std::string() const;

;

(3) 您现在可以将任何您喜欢的类方法添加到您的宠物类中。 例如。字符串运算符

    pet::operator std::string() const 
        switch (value) 
            case fish: return "fish";
            case cat:  return "cat";
            case dog:  return "dog";
            case bird: return "bird";
            case rabbit: return "rabbit";
            case other: return "Wow. How exotic of you!";
        
    

现在你可以使用例如 std::cout...

int main() 
    pet myPet = rabbit;
    if(myPet != fish) 
        cout << "No splashing! ";
    
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;

【讨论】:

完全兼容:如果您将枚举值与任何类型的类型推导一起使用,并且预计会获得pet 类型名/实例,则可以是模板、autodecltype,这会中断,因为您会得到 petEnum【参考方案6】:

它可能无法满足您的所有需求,但与非会员运营商一起,您仍然可以享受很多乐趣。例如:

#include <iostream>

enum class security_level

    none, low, medium, high
;

static bool operator!(security_level s)  return s == security_level::none; 

static security_level& operator++(security_level& s)

    switch(s)
    
        case security_level::none: s = security_level::low; break;
        case security_level::low: s = security_level::medium; break;
        case security_level::medium: s = security_level::high; break;
        case security_level::high: break;
    
    return s;


static std::ostream & operator<<(std::ostream &o, security_level s)

    switch(s)
    
        case security_level::none: return o << "none";
        case security_level::low: return o << "low";
        case security_level::medium: return o << "medium";
        case security_level::high: return o << "high";
    

这允许像这样的代码

security_level l = security_level::none;   
if(!!l)  std::cout << "has a security level: " << l << std::endl;  // not reached
++++l;
if(!!l)  std::cout << "has a security level: " << l << std::endl;  // reached: "medium"

【讨论】:

【参考方案7】:

基于jtlim's answer

想法(解决方案)

enum ErrorType: int 
  noConnection,
  noMemory
;

class Error 
public:
  Error() = default;
  constexpr Error(ErrorType type) : type(type)  

  operator ErrorType() const  return type; 
  constexpr bool operator == (Error error) const  return type == error.type; 
  constexpr bool operator != (Error error) const  return type != error.type;     
  constexpr bool operator == (ErrorType errorType) const  return type == errorType; 
  constexpr bool operator != (ErrorType errorType) const  return type != errorType; 

  String description()  
    switch (type) 
    case noConnection: return "no connection";
    case noMemory: return "no memory";
    default: return "undefined error";
    
 

private:
  ErrorType type;
;

用法

Error err = Error(noConnection);
err = noMemory;
print("1 " + err.description());

switch (err) 
  case noConnection: 
    print("2 bad connection");
    break;
  case noMemory:
    print("2 disk is full");
    break;
  default: 
    print("2 oops");
    break;


if (err == noMemory)  print("3 Errors match"); 
if (err != noConnection)  print("4 Errors don't match"); 

【讨论】:

以上是关于C++ 枚举类可以有方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

java-枚举

java实用类总结

C++枚举与字符串转换工具类

C++枚举与字符串转换工具类

C++ 和枚举以及类成员

SWIG 和 C++ 枚举类