基础枚举类继承

102

在C++中,是否有一种模式可以从另一个枚举类型继承?

类似于这样:

enum eBase 
{
   one=1, two, three
};


enum eDerived: public eBase
{
   four=4, five, six
};
14个回答

107
#include <iostream>
#include <ostream>

class Enum
{
public:
    enum
    {
        One = 1,
        Two,
        Last
    };
};

class EnumDeriv : public Enum
{
public:
    enum
    {
        Three = Enum::Last,
        Four,
        Five
    };
};

int main()
{
    std::cout << EnumDeriv::One << std::endl;
    std::cout << EnumDeriv::Four << std::endl;
    return 0;
}

3
我很困惑!你会如何在变量或函数参数中引用枚举类型,并确保期望枚举的函数不会接收到 EnumDeriv? - Sideshow Bob
26
这样行不通。 当您定义一些函数 int basic(EnumBase b) { return b; }int derived(EnumDeriv d) { return d; } 时,这些类型将无法转换为 int,尽管普通的枚举类型可以转换为 int。 当您尝试运行甚至像这个简单的代码:cout << basic(EnumBase::One) << endl;时,您会收到错误消息:conversion from ‘EnumBase::<anonymous enum>’ to non-scalar type ‘EnumBase’ requested。 可以通过添加一些转换运算符来解决这些问题。 - SasQ

74

不可能。枚举类型不支持继承。

你可以使用带有命名常量整数的类替代。

示例:

class Colors
{
public:
  static const int RED = 1;
  static const int GREEN = 2;
};

class RGB : public Colors
{
  static const int BLUE = 10;
};


class FourColors : public Colors
{
public:
  static const int ORANGE = 100;
  static const int PURPLE = 101;
};

这个解决方案有问题吗?例如,(我对多态性没有深入的理解)是否可以使用vector<Colors>并使用p = std::find(mycolors, mycolor+length, Colors::ORANGE)? - jespestana
2
@jespestana 不,你不会使用 Colors 类实例。你只需要使用静态常量成员中的 int 值。 - jiandingzhe
1
@jespestana 当然。另外,如果搜索是非常常见的操作,请考虑使用 flat_set 或开放地址哈希集。 - v.oddou
3
这个解决方案可能存在问题,因为这些值不再是独立的类型。你不能编写一个期望 Color 的函数,就像你可以针对枚举一样。 - Drew Dormann
2
最好将这些值声明为“constexpr”,而不是“const”。 - Red.Wave
显示剩余5条评论

12

您不能直接这样做,但可以尝试使用文章中的解决方案。

主要思路是使用辅助模板类来保存枚举值并具有类型转换运算符。考虑到枚举的基础类型为int,因此您可以在代码中无缝地使用这个持有者类来代替枚举。


1
虽然这段代码片段可能解决了问题,但包括解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您的代码建议的原因。 - NathanOliver
这是一个很棒的答案;它是那些“用不同的方式思考问题”的实例之一,使用模板的想法确实非常合适。 - Den-Jason
还要看一下这些与模板相关的解决方案: https://dev59.com/9G025IYBdhLWcg3wqn0N - Den-Jason

5
很遗憾,C++14不支持此功能。我希望C++17会有这样的语言特性。由于您已经得到了一些解决问题的方法,因此我不会提供解决方案。
我想指出的是,措辞应该是“扩展”,而不是“继承”。扩展允许更多的值(例如在您的示例中从3个跳到6个),而继承意味着对给定基类施加更多约束,因此可能性的集合会缩小。因此,潜在的强制类型转换将与继承完全相反。您可以将派生类强制转换为基类,但不能反过来进行类继承。但是,在具有扩展性时,您“应该”能够将基类强制转换为其扩展,并且不能反过来进行。我说“应该”是因为,正如我所说,这样的语言特性仍不存在。

请注意,extends是Eiffel语言中用于继承的关键字。 - Cheers and hth. - Alf
你是正确的,因为在这种情况下没有遵守里氏替换原则。由于这个原因,委员会不会接受看起来像继承语法的解决方案。 - v.oddou

4
这样怎么样?每个可能的值都会创建一个实例,但除此之外,它非常灵活。有什么缺点吗?
.h:
class BaseEnum
{
public:
  static const BaseEnum ONE;
  static const BaseEnum TWO;

  bool operator==(const BaseEnum& other);

protected:
  BaseEnum() : i(maxI++) {}
  const int i;
  static int maxI;
};

class DerivedEnum : public BaseEnum
{
public:
  static const DerivedEnum THREE;
};

.cpp:

int BaseEnum::maxI = 0;

bool BaseEnum::operator==(const BaseEnum& other) {
  return i == other.i;
}

const BaseEnum BaseEnum::ONE;
const BaseEnum BaseEnum::TWO;
const DerivedEnum DerivedEnum::THREE;

使用方法:

BaseEnum e = DerivedEnum::THREE;

if (e == DerivedEnum::THREE) {
    std::cerr << "equal" << std::endl;
}

我能看到的唯一缺点是更高的内存消耗和需要更多的代码行数。但我会尝试你的解决方案。 - Knitschi
我还将“BaseEnum :: i”公开,并将“BaseEnum :: maxI”设为私有。 - Knitschi
受保护的默认构造函数可能会成为问题,当枚举需要在第三方宏或模板中使用时,这些宏或模板需要一个默认构造函数。 - Knitschi

3
你可以使用一个名为 SuperEnum 的项目来创建可扩展的枚举。
/*** my_enum.h ***/
class MyEnum: public SuperEnum<MyEnum>
{
public:
    MyEnum() {}
    explicit MyEnum(const int &value): SuperEnum(value) {}

    static const MyEnum element1;
    static const MyEnum element2;
    static const MyEnum element3;
};

/*** my_enum.cpp ***/
const MyEnum MyEnum::element1(1);
const MyEnum MyEnum::element2;
const MyEnum MyEnum::element3;

/*** my_enum2.h ***/
class MyEnum2: public MyEnum
{
public:
    MyEnum2() {}
    explicit MyEnum2(const int &value): MyEnum(value) {}

    static const MyEnum2 element4;
    static const MyEnum2 element5;
};

/*** my_enum2.cpp ***/
const MyEnum2 MyEnum2::element4;
const MyEnum2 MyEnum2::element5;

/*** main.cpp ***/
std::cout << MyEnum2::element3;
// Output: 3

1
虽然这是一篇旧帖子,但我认为它值得回答。我建议摆脱默认构造函数,并将显式构造函数移至私有。您仍然可以以您正在进行的方式初始化变量。当然,您应该摆脱 const int&,改用简单的 int - Moia

2

如果您在派生类中使用相同名称定义enum并从基类中对应的enum的最后一项开始,您将获得几乎想要的继承枚举。请看以下代码:

class Base
{
public:
    enum ErrorType
    {
        GeneralError,
        NoMemory,
        FileNotFound,
        LastItem,
    };
};

class Inherited: public Base
{
public:
    enum ErrorType
    {
        SocketError = Base::LastItem,
        NotEnoughBandwidth,
    };
};

1
代码虽然可编译,但无法使用,因为编译器无法将 base::ErrorType 转换为 Inherited::ErrorType。 - bavaza
1
@bavaza,当将它们的值作为参数传递时,应该使用整数而不是枚举。 - Haspemulator

2
正如所述,枚举类型不应该有功能。因此,我采用了以下方法来解决您的问题,这是根据的回答进行修改的:
typedef struct
{
    enum
    {
        ONE = 1,
        TWO,
        LAST
    };
}BaseEnum;

typedef struct : public BaseEnum
{
    enum
    {
        THREE = BaseEnum::LAST,
        FOUR,
        FIVE
    };
}DerivedEnum;

2
这个解决方案存在一些问题。首先,您正在使用LAST污染BaseEnum,而LAST除了设置DerivedEnum的起始点之外并不存在。其次,如果我想在BaseEnum中明确设置一些值,并且这些值会与DerivedEnum的值发生冲突怎么办?无论如何,在C++14中这可能是我们能做到的最好的了。 - Огњен Шобајић
就像我之前所说的那样,这段代码是从一个先前的示例中改编而来的,因此它的表达方式是为了完整性而设计的。我并不关心之前的发帖者在他们的示例中存在逻辑问题。 - vigilance

2

这种方法可能有点巧妙,但在处理作用域枚举时,以下是我想出的解决方案:

最初的回答:

enum class OriginalType {
   FOO,  // 0
   BAR   // 1
   END   // 2
};

enum class ExtendOriginalType : std::underlying_type_t<OriginalType> {
   EXTENDED_FOO = static_cast<std::underlying_type_t<OriginalType>>
                                           (OriginalType::END), // 2
   EXTENDED_BAR  // 3
};

最初的回答,然后像这样使用:

OriginalType myOriginalType = (OriginalType)ExtendOriginalType::EXTENDED_BAR;

2

这个答案是Brian R. Bondy的一个变体。由于在评论中被要求,我将其添加为答案。不过我并没有指出它是否真的值得。

#include <iostream>

class Colors
{
public:
    static Colors RED;
    static Colors GREEN;

    operator int(){ return value; }
    operator int() const{ return value; }

protected:
    Colors(int v) : value{v}{} 

private:
    int value;
};

Colors Colors::RED{1};
Colors Colors::GREEN{2};

class RGB : public Colors
{
public:
    static RGB BLUE;

private:
    RGB(int v) : Colors(v){}
};

RGB RGB::BLUE{10};

int main ()
{
  std::cout << Colors::RED << " " << RGB::RED << std::endl;
}

Live at Coliru


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