【问题标题】:namespaces for enum types - best practices枚举类型的命名空间 - 最佳实践
【发布时间】:2010-10-03 17:20:07
【问题描述】:

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

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

例子:

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

【问题讨论】:

  • 首先,我会使用 Color::Red、Feeling:Angry 等
  • 好问题,我使用了命名空间方法.... ;)
  • 所有东西上的“c”前缀都会影响可读性。
  • 请注意,你不需要像enum e {...}那样命名枚举,枚举可以是匿名的,即enum {...},这在包装在命名空间或类中时更有意义。
  • 如果你有一个未命名的枚举,它的类型是什么?例如:枚举 FOO{};空栏(FOO e);但如果我们有 enum{} void bar2(???);

标签: c++ enums scope nested


【解决方案1】:

C++03 原答案:

namespace(相对于class)的好处是您可以在需要时使用using 声明。

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

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

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

较新的 C++11 建议:

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

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

【讨论】:

  • 我同意结构理念。并感谢您的赞美:)
  • +1 我不记得 C++11 的“枚举类”语法。如果没有该功能,枚举是不完整的。
  • 他们是否可以将“使用”用于“枚举类”的隐式范围。例如将添加“使用 Color::e;”编码允许使用“cRed”并知道这应该是 Color::e::cRed?
【解决方案2】:

我已经将前面的答案混合成这样的内容:(编辑:这仅对 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: ... ;
    }
}

【讨论】:

    【解决方案3】:

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

    enum class eColors { ... };
    enum class eFeelings { ... };
    

    【讨论】:

      【解决方案4】:

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

      #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:看起来很相似,是的。这大概是同一个想法。但是,除非您在其中添加许多“常用”方法,否则我不确定模板是否有益。
      • 1.不是真的。您可以通过 const_expr 或通过 int 的私有构造函数让编译时检查枚举是否有效。
      【解决方案5】:

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

      正如 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 位),并且适合该表示的所有内容都被视为有效值。

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

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

      【讨论】:

      • 有趣。你介意分享你的 Enum 类的定义吗?
      【解决方案6】:

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

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

      class Colors {
      public:
        enum TYPE {
          Red,
          Green,
          Blue
        };
      };
      
      template <typename T> void foo (T t) {
        typedef typename T::TYPE EnumType;
        // ...
      }
      

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

      【讨论】:

      • 不重新开课也是一个潜在的劣势。颜色列表也不是有限的。
      • 我认为不重新上课是一个潜在的优势。如果我想要更多颜色,我会用更多颜色重新编译这个类。如果我不能做到这一点(比如我没有代码),那么我无论如何都不想碰它。
      • @MSalters:无法重新开课不仅不是缺点,也是一种安全工具。因为当可以重新打开一个类并向枚举添加一些值时,它可能会破坏已经依赖于该枚举并且只知道旧值集的其他库代码。然后它会愉快地接受这些新值,但在运行时会因不知道如何处理它们而中断。记住开闭原则:类应该对修改关闭,但对扩展打开。通过扩展我的意思不是添加到现有代码,而是用新代码包装它(例如推导)。
      • 所以当你想扩展枚举时,你应该让它成为一个从第一个派生的新类型(如果它在 C++ 中很容易实现的话...... ;/ )。然后它可以被理解这些新值的新代码安全地使用,但只有旧值会被旧代码接受(通过将它们转换下来)。他们不应该接受这些新值中的任何一个类型错误(扩展的类型)。只有他们理解的旧值被接受为正确的(基本)类型(并且意外地也是新类型,因此它也可以被新代码接受)。
      【解决方案7】:

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

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

      【讨论】:

      • 枚举类...需要查一下!
      【解决方案8】:

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

      从你上面的例子:

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

      【讨论】:

      • 您能否提示一下为什么您更喜欢命名空间而不是类?
      • @xtofl : 你不能写'使用类颜色`
      • @MSalters:你也不能写Colors someColor = Red;,因为命名空间不构成类型。您必须改为写Colors::e someColor = Red;,这很违反直觉。
      • @SasQ 如果您想在switch 语句中使用Colors::e someColor,即使与struct/class 一起使用,您也不必使用它吗?如果您使用匿名 enum,则交换机将无法评估 struct
      • 抱歉,const e c 对我来说似乎难以阅读 :-) 不要那样做。但是,使用命名空间很好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-18
      • 1970-01-01
      • 2021-09-02
      • 1970-01-01
      • 2014-07-21
      • 2013-11-18
      • 1970-01-01
      相关资源
      最近更新 更多