111

通常,需要同时使用多个枚举类型。有时,一个人有一个名字冲突。想到了两个解决方案:使用命名空间,或使用“更大”的枚举元素名称。尽管如此,命名空间解决方案有两种可能的实现方式:一个具有嵌套枚举的虚拟类,或者一个完整的命名空间。

我正在寻找所有三种方法的优缺点。

例子:

// 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: //...
        //...
    }
  }
4

8 回答 8

81

原始 C++03 答案:

a (over a )的好处是您可以在需要时使用声明。namespaceclassusing

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

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

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

较新的 C++11 建议:

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

您将失去与enum class整数类型的隐式转换和比较,但在实践中,这可能会帮助您发现模棱两可或有缺陷的代码。

于 2009-01-27T17:16:45.380 回答
21

仅供参考,在 C++0x 中,您提到的情况有一种新语法(请参阅C++0x wiki 页面

enum class eColors { ... };
enum class eFeelings { ... };
于 2009-01-27T21:55:27.267 回答
12

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

我有一个包含我所有项目枚举的大头文件,因为这些枚举在工作类之间共享,将枚举放在工作类本身中是没有意义的。

struct避免了 public: 语法糖,并且允许您在其他工作类中实际声明这些枚举的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: ... ;
    }
}
于 2013-02-20T19:20:20.003 回答
10

我肯定会避免为此使用课程;改用命名空间。问题归结为是使用命名空间还是对枚举值使用唯一 ID。就个人而言,我会使用命名空间,以便我的 id 可以更短,并且希望更不言自明。然后应用程序代码可以使用“使用命名空间”指令并使所有内容更具可读性。

从你上面的例子:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
于 2009-01-27T09:39:34.537 回答
8

使用类的好处是你可以在它之上构建一个成熟的类。

#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语句。

于 2012-04-22T15:41:18.637 回答
7

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

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

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

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

就个人而言,我不喜欢using,而且我更喜欢完全限定的名称,所以我并不认为这是命名空间的优点。但是,这可能不是您在项目中做出的最重要的决定!

于 2009-01-27T10:58:48.043 回答
5

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

当然,当我们使用 C++0x 获得枚举类时,这一切都没有实际意义。

于 2009-01-27T15:25:37.723 回答
3

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

正如 Richard Corden 所指出的,类的好处是它是 c++ 意义上的类型,因此您可以将它与模板一起使用。

我有特殊的 toolbox::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 位),并且适合该表示的所有内容都被认为是有效值。

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

总而言之,我的小帮手确实为我的枚举轻松了一些事情(当然,它增加了一些开销),而且这只是可能的,因为我将每个枚举嵌套在它自己的结构中:)

于 2009-09-24T06:47:53.193 回答