枚举类型的命名空间 - 最佳实践
Posted
技术标签:
【中文标题】枚举类型的命名空间 - 最佳实践【英文标题】:namespaces for enum types - best practices 【发布时间】:2010-10-03 17:20:07 【问题描述】:通常,需要同时使用多个枚举类型。有时,一个人有一个名字冲突。想到了两个解决方案:使用命名空间,或使用“更大”的枚举元素名称。不过,命名空间解决方案有两种可能的实现方式:具有嵌套枚举的虚拟类,或完整的命名空间。
我正在寻找所有三种方法的优缺点。
例子:
// oft seen hand-crafted name *** 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(???);
【参考方案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;
如上例所示,通过使用类,您可以:
-
禁止(遗憾的是,不是编译时)C++ 允许从无效值进行强制转换,
为新创建的枚举设置一个(非零)默认值,
添加更多方法,例如返回选项的字符串表示形式。
请注意,您需要声明 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
感觉就像一个健壮的对象(参见struct
与class
的辩论)。命名空间的一个可能好处是它可以在以后扩展 - 如果您正在处理无法修改的第三方代码,这很有用。
当然,当我们使用 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
对我来说似乎难以阅读 :-) 不要那样做。但是,使用命名空间很好。以上是关于枚举类型的命名空间 - 最佳实践的主要内容,如果未能解决你的问题,请参考以下文章