枚举类型的名称空间-最佳做法


102

通常,一个人同时需要几种枚举类型。有时,一个人会发生名字冲突。有两种解决方案:使用名称空间,或使用“较大”的枚举元素名称。命名空间解决方案仍然有两种可能的实现:具有嵌套枚举的虚拟类或完整的命名空间。

我正在寻找这三种方法的利弊。

例:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
首先,我将使用Color :: Red,Feeling:Angry等
abatishchev

好问题,我用了命名空间方法。...;)
MiniScalope 2010年

18
所有内容上的“ c”前缀都会损害可读性。
用户

8
@User:为什么,谢谢您开启匈牙利的讨论:)
xtofl 2011年

5
请注意,您无需像中那样命名枚举enum e {...},枚举可以是匿名的,即enum {...},将其包装在名称空间或类中时更有意义。
kralyk'4

Answers:


73

原始C ++ 03答案:

益处namespace(在class)是您可以使用using,当你想声明。

使用a 的问题namespace是可以在代码中的其他地方扩展名称空间。在大型项目中,不能保证两个不同的枚举不会同时认为它们被称为eFeelings

对于看起来更简单的代码,我使用了struct,因为您大概希望内容是公开的。

如果您正在执行这些实践中的任何一种,那么您将处于领先地位,并且可能不需要进一步检查。

较新的C ++ 11建议:

如果您使用的是C ++ 11或更高版本,enum class则将枚举值隐式限制在枚举名称内。

这样,enum class您将失去对整数类型的隐式转换和比较,但实际上,这可能会帮助您发现模棱两可或错误的代码。


4
我同意struct-idea。并感谢您的赞美:)
xtofl

3
+1我不记得C ++ 11的“枚举类”语法。没有该功能,枚举是不完整的。
Grault

他们是否可以将“使用”用于“枚举类”的隐式范围?例如,将添加“ using Color :: e;” 代码允许使用“ cRed”,知道这应该是Color :: e :: cRed吗?
JVApen 2015年


11

我已经将前面的答案混合成这样的东西:(编辑:这仅对C ++ 11之前的版本有用。如果您使用的是C ++ 11,请使用enum class

我有一个包含所有项目枚举的大头文件,因为这些枚举在辅助类之间共享,并且将枚举放入辅助类本身没有任何意义。

struct避免市民:语法糖,以及typedef让你实际上其他工人类中声明这些枚举变量。

我认为使用名称空间根本没有帮助。也许这是因为我是C#程序员,并且在引用值时必须使用枚举类型名称,所以我已经习惯了。

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

我绝对会避免为此使用类;改用名称空间。这个问题归结为对于枚举值使用命名空间还是使用唯一的ID。就个人而言,我将使用命名空间,以便我的ID可以更短,并且希望更容易解释。然后,应用程序代码可以使用“ using namespace”指令,并使所有内容更具可读性。

从上面的示例中:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

您能否暗示为什么偏爱名称空间而不是类?
xtofl,2009年

@xtofl:您不能编写“使用颜色类”
MSalters,2009年

2
@MSalters:您也不能编写Colors someColor = Red;,因为名称空间不构成类型。您必须改为编写Colors::e someColor = Red;,这很违反直觉。
SasQ 2012年

@SasQ你岂不使用Colors::e someColor了,甚至struct/class如果你想在一个使用它switch的语句?如果您使用匿名方式,enum则该交换机将无法评估struct
麦克白的《谜》,

1
抱歉,但是const e c对我来说似乎很难理解:-)不要那样做。但是,使用名称空间就可以了。
dhaumann '16

7

使用类或名称空间的区别在于,不能像名称空间那样重新打开该类。这避免了将来可能会滥用名称空间的可能性,但是也存在您也无法添加到枚举集的问题。

使用类的可能好处是它们可以用作模板类型参数,而名称空间则不是这样:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

就个人而言,我不喜欢使用,我更喜欢使用完全限定的名称,因此我并没有真正将其视为名称空间的加号。但是,这可能不是您要在项目中做出的最重要的决定!


不重新开放课程也是潜在的不利条件。颜色列表也不是有限的。
MSalters

1
我认为不改班是一个潜在的优势。如果我想要更多的颜色,则只需使用更多的颜色重新编译该类。如果我不能这样做(例如我没有代码),那么无论如何我都不想碰它。
Thomas Eding

@MSalters:重开课程的可能性不仅是缺点,还是安全工具。因为当有可能重新打开一个类并向枚举添加一些值时,它可能破坏其他已经依赖于该枚举并且仅知道旧值集的库代码。然后它将很高兴地接受这些新值,但在运行时因不知道如何处理它们而中断。记住开放式封闭原则:类应关闭以进行修改,但应打开以进行扩展。通过扩展,我的意思是不添加现有代码,而是将其与新代码一起包装(例如,派生)。
SasQ 2012年

因此,当您想扩展枚举时,应该将其设为从第一个枚举派生的新类型(如果只有这种类型在C ++ ...; /中很容易实现)。然后可以由理解这些新值的新代码安全地使用它,但是旧代码仅接受(通过将它们向下转换)旧值。他们不应接受这些新值中的任何一个类型错误(扩展的类型)。只有他们理解的旧值才被接受为正确的(基本)类型(并且偶然也为新类型,因此新代码也可以接受)。
SasQ 2012年

7

使用类的优点是您可以在其上构建完整的类。

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

如以上示例所示,通过使用类,您可以:

  1. 禁止(不幸的是,不是编译时)C ++允许从无效值进行强制转换,
  2. 为新创建的枚举设置一个(非零)默认值,
  3. 添加其他方法,例如返回选择的字符串表示形式。

请注意,您需要声明,operator enum_type()以便C ++知道如何将您的类转换为基础枚举。否则,您将无法将类型传递给switch语句。


此解决方案与此处显示的内容有某种联系吗?:en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum我正在考虑如何使其成为模板,而不必每次都重写此模式我需要使用它。
SasQ 2012年

@SasQ:看起来类似,是的。那可能是相同的想法。但是,除非您在其中添加了很多“通用”方法,否则我不确定模板是否有益。
米哈尔戈尔诺-

1.不是真的。您可以通过const_expr或通过int的私有构造函数进行编译时检查,以确保该枚举有效。
xryl669 2014年

5

由于枚举的作用域仅限于其封闭范围,因此最好将它们包装在某种东西中,以避免污染全局名称空间并有助于避免名称冲突。我更喜欢将名称空间归类,因为namespace感觉就像是一堆东西,而class感觉像是一个健壮的对象(参见structvs. class辩论)。命名空间的一个可能好处是可以在以后进行扩展-如果您正在处理无法修改的第三方代码,则很有用。

当我们使用C ++ 0x获得枚举类时,这当然没有什么意义。


枚举类...需要查找!
xtofl,2009年

3

我也倾向于将枚举封装在类中。

正如理查德·科登(Richard Corden)所说,类的好处是它是c ++的类型,因此可以将其与模板一起使用。

我有一个特殊的工具箱:: Enum类,可以满足我的需要,这些类专门针对提供基本功能的每个模板(主要是:将枚举值映射到std :: string,以便于阅读I / O)。

我的小模板还具有真正检查允许值的附加好处。编译器有点松懈,无法检查值是否确实在枚举中:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

总是让我感到困扰的是,编译器不会捕捉到这一点,因为您留下的枚举值没有意义(而且您不会期望)。

类似地:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

main再次返回错误。

问题在于,编译器将枚举以可用的最小表示形式进行拟合(此处需要2位),并且适合该表示形式的所有内容均被视为有效值。

还有一个问题是,有时您宁愿在可能的值上循环而不是使用开关,这样就不必在每次向枚举添加值时都修改所有开关。

总而言之,我的小帮手确实为我的枚举简化了工作(当然,这会增加一些开销),并且这是唯一可能的,因为我将每个枚举都嵌套在自己的结构中:)


4
有趣。您介意分享Enum类的定义吗?
momeara
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.