枚举types的命名空间 – 最佳实践
通常需要几个枚举types。 有时候,有一个名字冲突。 想到这两个解决scheme:使用一个名称空间,或使用“更大的”枚举元素名称。 尽pipe如此,命名空间解决scheme有两个可能的实现:一个嵌套枚举的虚拟类,或者一个完整的命名空间。
我正在寻找所有三种方法的优点和缺点。
例:
// 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::ec ) { 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::ec ) { switch (c) { default: assert(false); break; case Colors::cRed: //... break; case Colors::cBlue: //... //... } }
namespace
(超过一个class
)的好处是你可以在你需要的时候using
声明。
使用namespace
的问题是名称空间可以在代码中的其他地方扩展。 在一个大型项目中,你不能保证两个不同的枚举都不认为它们被称为eFeelings
为了看起来更简单的代码,我使用一个struct
,因为你大概想要的内容是公开的。
如果你正在做这些实践,那么你就超前了,可能不需要进一步细看。
这个相当过时的答案的附录:
如果您使用的是C ++ 11或更高版本,则enum class
将隐式地将枚举值范围限定在枚举的名称中。
使用enum class
你将失去隐式转换和整数types的比较,但在实践中,可能会帮助您标记不明确或错误的代码。
FYI在C ++ 0x有一个新的语法,像你提到的情况(见C ++ 0x维基页面 )
enum class eColors { ... }; enum class eFeelings { ... };
我肯定会避免为此使用课堂; 改用一个名字空间。 问题归结为是使用名称空间还是使用枚举值的唯一标识符。 就个人而言,我会使用一个命名空间,以便我的ID可以更短,希望更多的自我解释。 然后应用程序代码可以使用“使用命名空间”指令,使一切更具可读性。
从你上面的例子:
using namespace Colors; void setPenColor( const ec ) { switch (c) { default: assert(false); break; case cRed: //... break; case cBlue: //... //... } }
使用类或名称空间的区别在于类不能像命名空间那样重新打开。 这样可以避免命名空间将来可能被滥用的可能性,但也存在无法添加到枚举集合的问题。
使用类的一个可能的好处是,它们可以用作模板types参数,而命名空间则不是这种情况:
class Colors { public: enum TYPE { Red, Green, Blue }; }; template <typename T> void foo (T t) { typedef typename T::TYPE EnumType; // ... }
就个人而言,我不喜欢使用 ,而我更喜欢完全限定的名称,所以我不认为这是名称空间的优点。 但是,这可能不是您在项目中做出的最重要的决定!
使用一个类的好处是你可以在它上面build立一个完整的类。
#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; } }
如上例所示,通过使用一个类,您可以:
- 禁止(不幸的是,不是编译时)C ++允许从无效值转换,
- 为新创build的枚举设置一个(非零)缺省值,
- 添加更多的方法,比如返回select的string表示。
请注意,您需要声明operator enum_type()
以便C ++知道如何将您的类转换为基础枚举。 否则,您将无法将types传递给switch
语句。
我已经混合了前面的答案,像这样:(编辑:这只是有用的预C ++ 11。如果您使用C ++ 11,使用enum class
)
我有一个大的头文件,其中包含我所有的项目枚举,因为这些枚举在工作类之间共享,将枚举放在工作类本身中是没有意义的。
这个struct
避免了public:syntactic sugar, typedef
让你实际上在其他worker类中声明这些枚举的variables。
我不认为使用命名空间是有帮助的。 也许这是因为我是C#程序员,并且在引用值时必须使用枚举types名称,所以我已经习惯了。
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: ... ; } }
由于枚举的范围在它们的封闭范围内,因此最好将它们包装在一些内容中,以避免污染全局名称空间并避免名称冲突。 我更喜欢一个命名空间,因为namespace
感觉像是一个控制包,而class
感觉像一个健壮的对象(比较struct
与class
辩论)。 命名空间的一个可能的好处是,它可以稍后扩展 – 如果你正在处理你不能修改的第三方代码,那么它很有用。
当我们用C ++ 0x获得枚举类时,这当然是没有意义的。
我也倾向于把我的枚举包装在课堂上。
正如理查德·科登(Richard Corden)所指出的那样,类的好处在于它是一种c ++types的types,所以可以在模板中使用它。
我有特殊的工具箱::枚举类为我的需要,我专门为每个提供基本function的模板(主要:映射一个枚举值到一个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位)的枚举,并且适合这种表示的所有东西都被认为是一个有效的值。
还有一个问题是,有时候你宁愿在可能的值上设置一个循环,而不是一个开关,这样你就不必每次在枚举中增加一个值就修改所有的开关。
总而言之,我的小帮手确实为我的枚举减轻了一些负担(当然,它增加了一些开销),而且这是唯一可能的,因为我将每个枚举嵌套在自己的struct中:)