【发布时间】:2010-12-20 18:18:12
【问题描述】:
C++ 中有没有办法扩展/“继承”枚举?
IE:
enum Enum {A,B,C};
enum EnumEx : public Enum {D,E,F};
或者至少定义它们之间的转换?
【问题讨论】:
-
有一篇很有意思的文章,附了一些代码,这里:codeproject.com/Articles/32000/…
C++ 中有没有办法扩展/“继承”枚举?
IE:
enum Enum {A,B,C};
enum EnumEx : public Enum {D,E,F};
或者至少定义它们之间的转换?
【问题讨论】:
http://www.codeproject.com/KB/cpp/InheritEnum.aspx 遍历一个创建扩展枚举的方法。
创建 InheritEnum.h:
// -- InheritEnum.h
template <typename EnumT, typename BaseEnumT>
class InheritEnum
{
public:
InheritEnum() {}
InheritEnum(EnumT e)
: enum_(e)
{}
InheritEnum(BaseEnumT e)
: baseEnum_(e)
{}
explicit InheritEnum( int val )
: enum_(static_cast<EnumT>(val))
{}
operator EnumT() const { return enum_; }
private:
// Note - the value is declared as a union mainly for as a debugging aid. If
// the union is undesired and you have other methods of debugging, change it
// to either of EnumT and do a cast for the constructor that accepts BaseEnumT.
union
{
EnumT enum_;
BaseEnumT baseEnum_;
};
};
然后使用:
enum Fruit { Orange, Mango, Banana };
enum NewFruits { Apple, Pear };
typedef InheritEnum< NewFruit, Fruit > MyFruit;
void consume(MyFruit myfruit);
YMMV.
【讨论】:
我这样做
enum OPC_t // frame Operation Codes
{
OPC_CVSND = 0 // Send CV value
, OPC_CVREQ = 1 // Request CV (only valid for master app)
, OPC_COMND = 2 // Command
, OPC_HRTBT = 3 // Heart Beat
};
enum rxStatus_t // this extends OPC_t
{
RX_CVSND = OPC_CVSND // Send CV value
, RX_CVREQ = OPC_CVREQ // Request CV
, RX_COMND = OPC_COMND // Command
, RX_HRTBT = OPC_HRTBT // Heart Beat
, RX_NONE // No new Rx
, RX_NEWCHIP // new chip detected
};
【讨论】:
大家可以试试这个:)
struct EnumType
{
enum
{
None = 0,
Value_1 = 1,
Value_2 = 2,
Value_3 = 3
};
//For when using the EnumType as a variable type
int Value { None };
};
struct EnumTypeEx : EnumType
{
enum
{
ExValue_1 = 3,
ExValue_2 = 4,
//override the value of Value_3
Value_3 = 3000
};
};
优点:
缺点:
(*) 您可以从最后一个基值开始自动增加值,只需明确地编写它,例如。 ExValue_Start = LastBaseValue。
【讨论】:
我是这样解决的:
typedef enum
{
#include "NetProtocols.def"
} eNetProtocols, eNP;
当然,如果在 NetProtocols.def 文件中添加新的 net 协议,则必须重新编译,但至少可以扩展。
“NetProtocols.def”将仅包含字段名称:
HTTP,
HTTPS,
FTP
【讨论】:
我在一些在我设计的小型硬件设备上运行的项目中遇到了这个问题。有一个包含许多服务的通用项目。其中一些服务使用枚举作为参数来获得额外的类型检查和安全性。我需要能够在使用这些服务的项目中扩展这些枚举。
正如其他人所提到的,c++ 不允许您扩展枚举。但是,您可以使用具有 enum 类 的所有优点的命名空间和模板来模拟枚举。
枚举类有以下好处:
现在,如果您将一个类定义为 enum,则不能在类声明中创建 enum 的 constexpr 实例,因为类还没有完成,它会导致编译错误。此外,即使这有效,您也无法稍后在另一个文件/子项目中轻松扩展枚举值集。
现在命名空间没有这样的问题,但它们不提供类型安全。
答案是首先创建一个模板化的基类,它允许不同基数大小的枚举,这样我们就不会浪费我们不使用的东西。
template <typename TYPE>
class EnumClass {
private:
TYPE value_;
public:
explicit constexpr EnumClass(TYPE value) :
value_(value){
}
constexpr EnumClass() = default;
~EnumClass() = default;
constexpr explicit EnumClass(const EnumClass &) = default;
constexpr EnumClass &operator=(const EnumClass &) = default;
constexpr operator TYPE() const {return value_;}
constexpr TYPE value() const {return value_;}
};
然后对于我们想要扩展和模拟的每个 枚举类,我们创建一个命名空间和一个像这样的类型:
namespace EnumName {
class Type :public Enum<uint8_t> {
public:
explicit constexpr Type(uint8_t value): Enum<uint8_t>(value){}
constexpr Enum() = default;
}
constexpr auto Value1 = Type(1);
constexpr auto Value2 = Type(2);
constexpr auto Value3 = Type(3);
}
如果你已经包含了原始的 EnumName,那么稍后在你的代码中你可以这样做:
namespace EnumName {
constexpr auto Value4 = Type(4U);
constexpr auto Value5 = Type(5U);
constexpr auto Value6 = Type(6U);
constexpr std::array<Type, 6U> Set = {Value1, Value2, Value3, Value4, Value5, Value6};
}
现在你可以像这样使用枚举了:
#include <iostream>
void fn(EnumName::Type val){
if( val != EnumName::Value1 ){
std::cout << val;
}
}
int main(){
for( auto e :EnumName::Set){
switch(e){
case EnumName::Value1:
std::cout << "a";
break;
case EnumName::Value4:
std::cout << "b";
break;
default:
fn(e);
}
}
}
所以我们有一个 case 语句、枚举比较、参数类型安全和所有可扩展的。请注意,该集合是 constexpr 并且最终不会在小型微型计算机上使用宝贵的 RAM(在 Godbolt.org 上验证的位置。:-)。作为奖励,我们能够迭代一组枚举值。
【讨论】:
实际上你可以通过一种方式来扩展枚举。
C++ 标准将有效枚举值定义为基础类型的所有有效值,因此以下是有效的 C++ (11+)。它不是未定义的行为,但它非常讨厌 - 你已被警告过。
#include <cstdint>
enum Test1:unit8_t {
Value1 =0,
Value2 =1
};
constexpr auto Value3 = static_cast<Test1>(3);
constexpr auto Value4 = static_cast<Test1>(4);
constexpr auto Value5 = static_cast<Test1>(5);
Test1 fn(Test1 val){
switch(val){
case Value1:
case Value2:
case Value3:
case Value4:
return Value1;
case Value5:
return Value5;
}
}
int main(){
return static_cast<uint8_t>(fn(Value5));
}
请注意,大多数编译器不会将附加值视为生成有关 switch 语句中缺少枚举值的警告的集合的一部分。所以 如果缺少 Value2,clang 和 gcc 将发出警告,但如果在上述 switch 语句中缺少 Value4,则不会执行任何操作。
【讨论】:
对于这种 c++ 差距,一个简单但有用的解决方法如下:
#define ENUM_BASE_VALS A,B,C
enum Enum {ENUM_BASE_VALS};
enum EnumEx {ENUM_BASE_VALS, D,E,F};
【讨论】:
只是一个想法:
您可以尝试为每个常量创建一个空类(也许将它们全部放在同一个文件中以减少混乱),为每个类创建一个实例并将指向这些实例的指针用作“常量”。这样,编译器将理解继承,并在使用函数调用时执行任何必要的 ChildPointer-to-ParentPointer 转换,并且编译器仍然会进行类型安全检查,以确保没有人将无效的 int 值传递给函数(这将有如果您使用 LAST 值方法“扩展”枚举,则使用该方法)。
虽然还没有完全考虑到这一点,所以欢迎任何采用这种方法的 cmets。
一旦有时间,我会尝试发布一个示例来说明我的意思。
【讨论】:
如果您能够创建枚举的子类,它就必须反过来工作。
子类中的实例集是超类中实例的子集。想想标准的“形状”示例。 Shape 类表示所有形状的集合。 Circle 类,它的子类,代表圆形的子集。
为了保持一致,枚举的子类必须包含它所继承的枚举中元素的子集。
(不,C++ 不支持这个。)
【讨论】:
enum A {1, 65525}; 并且编译器决定使用 16 位无符号整数来表示它。现在假设我定义enumEx : public Enum { 131071 };。这个 EnumEx 类型的对象不可能作为 Enum 的实例传递,它实际上会被切片。哎呀。这就是为什么您需要 C++ 中的指针来执行运行时多态性。我猜 C++ 可以使每个枚举都成为可能的最大枚举的大小。但从概念上讲,值 131071 不应该是 Enum 的有效实例。
struct S{ enum {A,B,C}; }; struct T : S {enum {D=3,E,F};};,所以现在T,作为S 的子类,仍然包含元素的超集:A,B,C,D, E 和 F
不,没有。
enum 在 C++ 中确实是个可怜的东西,这当然是不幸的。
即使是 C++0x 中引入的 class enum 也没有解决这个可扩展性问题(尽管它们至少为类型安全做了一些事情)。
enum 的唯一优点是它们不存在:它们提供了一些类型安全性,同时由于它们被编译器直接替换,因此不会产生任何运行时开销。
如果你想要这样的野兽,你必须自己动手:
MyEnum,其中包含一个int(基本上)您现在可以随意扩展您的类(添加命名构造函数)...
虽然这是一种解决方法,但我从未找到处理枚举的令人满意的方法......
【讨论】:
以下代码运行良好。
enum Enum {A,B,C};
enum EnumEx {D=C+1,E,F};
【讨论】: