C ++中使用哪种Typesafe枚举?

众所周知,C ++中的内置枚举不是types安全的。 我想知道哪些类实现types安全枚举在那里使用…我自己使用下面的“自行车”,但它有点冗长和有限:

typesafeenum.h:

struct TypesafeEnum { // Construction: public: TypesafeEnum(): id (next_id++), name("") {} TypesafeEnum(const std::string& n): id(next_id++), name(n) {} // Operations: public: bool operator == (const TypesafeEnum& right) const; bool operator != (const TypesafeEnum& right) const; bool operator < (const TypesafeEnum& right) const; std::string to_string() const { return name; } // Implementation: private: static int next_id; int id; std::string name; }; 

typesafeenum.cpp:

 int TypesafeEnum::next_id = 1; bool TypesafeEnum::operator== (const TypesafeEnum& right) const { return id == right.id; } bool TypesafeEnum::operator!= (const TypesafeEnum& right) const { return !operator== (right); } bool TypesafeEnum::operator< (const TypesafeEnum& right) const { return id < right.id; } 

用法:

 class Dialog { ... struct Result: public TypesafeEnum { static const Result CANCEL("Cancel"); static const Result OK("Ok"); }; Result doModal(); ... }; const Dialog::Result Dialog::Result::OK; const Dialog::Result Dialog::Result::CANCEL; 

另外:我想我应该对这个要求更具体一些。 我会尽量总结一下:

优先级1:将枚举variables设置为无效值应该是不可能的(编译时错误),没有例外。

优先级2:将枚举值转换为/从一个int应该可能与一个显式的函数/方法调用。

优先级3:尽可能紧凑,优雅和方便的声明和使用

优先级4:将枚举值转换为string和从string转换枚举值。

优先级5 :(很高兴)可以迭代枚举值。

我目前正在使用Boost Vault中的Boost.Enum提案(文件enum_rev4.6.zip )。 虽然它从未被正式提交join到Boost中,但它是可用的。 (缺less文档,但是由明确的源代码和良好的testing来弥补。)

Boost.Enum让你像这样声明一个枚举:

 BOOST_ENUM_VALUES(Level, const char*, (Abort)("unrecoverable problem") (Error)("recoverable problem") (Alert)("unexpected behavior") (Info) ("expected behavior") (Trace)("normal flow of execution") (Debug)("detailed object state listings") ) 

并让它自动扩展到这个:

 class Level : public boost::detail::enum_base<Level, string> { public: enum domain { Abort, Error, Alert, Info, Trace, Debug, }; BOOST_STATIC_CONSTANT(index_type, size = 6); Level() {} Level(domain index) : boost::detail::enum_base<Level, string>(index) {} typedef boost::optional<Level> optional; static optional get_by_name(const char* str) { if(strcmp(str, "Abort") == 0) return optional(Abort); if(strcmp(str, "Error") == 0) return optional(Error); if(strcmp(str, "Alert") == 0) return optional(Alert); if(strcmp(str, "Info") == 0) return optional(Info); if(strcmp(str, "Trace") == 0) return optional(Trace); if(strcmp(str, "Debug") == 0) return optional(Debug); return optional(); } private: friend class boost::detail::enum_base<Level, string>; static const char* names(domain index) { switch(index) { case Abort: return "Abort"; case Error: return "Error"; case Alert: return "Alert"; case Info: return "Info"; case Trace: return "Trace"; case Debug: return "Debug"; default: return NULL; } } typedef boost::optional<value_type> optional_value; static optional_value values(domain index) { switch(index) { case Abort: return optional_value("unrecoverable problem"); case Error: return optional_value("recoverable problem"); case Alert: return optional_value("unexpected behavior"); case Info: return optional_value("expected behavior"); case Trace: return optional_value("normal flow of execution"); case Debug: return optional_value("detailed object state listings"); default: return optional_value(); } } }; 

它满足你列出的所有五个优先事项。

一个很好的折衷方法是这样的:

 struct Flintstones { enum E { Fred, Barney, Wilma }; }; Flintstones::E fred = Flintstones::Fred; Flintstones::E barney = Flintstones::Barney; 

它不像您的版本那样是types安全的,但是使用比标准枚举更好,并且在您需要时仍然可以利用整数转换。

我使用C ++ 0x typesafe枚举 。 我使用一些帮助模板/macros来提供string的function。

 enum class Result { Ok, Cancel}; 

我不。 太多的开销太less,没有什么好处。 此外,能够枚举枚举到不同的数据types序列化是一个非常方便的工具。 我从来没有见过一个“types安全”枚举值得C ++提供足够好的实现的开销和复杂性的实例。

我的意思是,你正在发明一个问题,然后解决它。 我认为没有必要为枚举值做一个详细的框架。 如果你致力于让你的价值观成为某个特定集合的成员,你可以修改一个唯一集合数据types的变体。

我个人使用了types安全枚举成语的改编版本。 它没有提供你在编辑中陈述的所有五个“要求”,但是我坚决不同意它们中的一些。 例如,我看不出Prio#4(将值转换为string)与types安全性有什么关系。 大多数情况下,单个值的string表示应该与types的定义分开(想一想为什么i18n)。 Prio#5(iteratio,这是可选的)是我希望在枚举中自然发生的最好的事情之一,所以我感到很难过,它在请求中显示为“可选”,但似乎更好地解决一个单独的迭代系统 ,如begin / end函数或enum_iterator,这使得它们可以与STL和C ++ 11无缝地工作。

OTOH这个简单的习惯用法很好地提供了Prio#3 Prio#1,这是因为它大部分只包含更多的types信息。 更何况这是一个非常简单的解决scheme,大多数情况下不需要任何外部依赖头文件,所以很容易随身携带。 它还具有使列举范围为a-la-C ++ 11的优点:

 // This doesn't compile, and if it did it wouldn't work anyway enum colors { salmon, .... }; enum fishes { salmon, .... }; // This, however, works seamlessly. struct colors_def { enum type { salmon, .... }; }; struct fishes_def { enum type { salmon, .... }; }; typedef typesafe_enum<colors_def> colors; typedef typesafe_enum<fishes_def> fishes; 

解决scheme提供的唯一“洞”是它没有解决这样的事实,即它不能阻止不同types的enum (或enum和int)被直接比较,因为当直接使用值时,隐式转换为int

 if (colors::salmon == fishes::salmon) { .../* Ooops! */... } 

但是到目前为止,我发现这样的问题可以通过简单地提供一个比较好的编译器来解决 – 例如,明确地提供一个运算符来比较任何两个不同的enumtypes,然后强制它失败:

 // I'm using backports of C++11 utilities like static_assert and enable_if template <typename Enum1, typename Enum2> typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool > ::type operator== (Enum1, Enum2) { static_assert (false, "Comparing enumerations of different types!"); } 

尽pipe到目前为止,它似乎没有破坏代码,并且没有做别的事情就明确地处理具体问题,但我不确定这种事情是“ 应该 ”做的事情(我怀疑它会干扰enum已经参与了在其他地方声明的转换运算符,我很乐意收到关于这个的评论)。

结合上面的types安全方法,可以在不需要做任何模糊处理的情况下,提供一些相对接近C ++ 11 enum class可读性(可读性和可维护性)。 我不得不承认这很有趣,我从来没有想过要编译器我是否在处理enum

我认为Java enum将是一个很好的模型。 基本上,Java表单看起来像这样:

 public enum Result { OK("OK"), CANCEL("Cancel"); private final String name; Result(String name) { this.name = name; } public String getName() { return name; } } 

Java方法的有趣之处在于OKCANCEL是不可变的, Result单例实例(用你看到的方法)。 您不能创build任何进一步的Result实例。 由于他们是单身人士,你可以通过指针/参考比较 – 非常方便。 🙂

ETA:在Java中,不是用手工完成位掩码,而是使用EnumSet来指定一个位集合(它实现了Set接口,并且像集合一样工作—但是使用位掩码来实现)。 比手写的位掩码操作更可读!

我在这里就另一个话题给出了答案。 这是一种不同的风格,它允许大部分相同的function,而不需要修改原来的枚举定义(因此允许在没有定义枚举的情况下使用)。 它也允许运行时间范围检查。

我的方法的缺点是,它不会以编程方式强制枚举和辅助类之间的耦合,所以它们必须并行更新。 它适用于我,但YMMV。

我正在编写我自己的types安全枚举库在https://bitbucket.org/chopsii/typesafe-enums

我不是迄今为止最有经验的C ++开发人员,但是由于BOOST Vault枚举的缺点,我正在写这个。

随意检查一下,并自己使用它们,但是它们有一些(希望是轻微的)可用性问题,可能根本不是跨平台的。

如果你愿意,请投稿。 这是我的第一个开源项目。

使用boost::variant

在尝试了很多上述想法之后,发现他们缺乏这个简单的方法:

 #include <iostream> #include <boost/variant.hpp> struct A_t {}; static const A_t A = A_t(); template <typename T> bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; } struct B_t {}; static const B_t B = B_t(); template <typename T> bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; } struct C_t {}; static const C_t C = C_t(); template <typename T> bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; } typedef boost::variant<A_t, B_t> AB; typedef boost::variant<B_t, C_t> BC; void ab(const AB & e) { if(isA(e)) std::cerr << "A!" << std::endl; if(isB(e)) std::cerr << "B!" << std::endl; // ERROR: // if(isC(e)) // std::cerr << "C!" << std::endl; // ERROR: // if(e == 0) // std::cerr << "B!" << std::endl; } void bc(const BC & e) { // ERROR: // if(isA(e)) // std::cerr << "A!" << std::endl; if(isB(e)) std::cerr << "B!" << std::endl; if(isC(e)) std::cerr << "C!" << std::endl; } int main() { AB a; a = A; AB b; b = B; ab(a); ab(b); ab(A); ab(B); // ab(C); // ERROR // bc(A); // ERROR bc(B); bc(C); } 

你可能会想出一个macros来生成样板。 (让我知道如果你这样做。)

与其他方法不同,这个方法实际上是types安全的,可以和旧的C ++一起工作。 你甚至可以使用像boost::variant<int, A_t, B_t, boost::none>这样的很酷的types,例如,表示一个可以是A,B,一个整数或者几乎没有的值,它几乎是Haskell98的types安全级别。

下行要注意:

  • 至less在旧的提升 – 我在一个系统提升1.33 – 你的变种限制在20个项目; 有一个解决办法然而
  • 影响编译时间
  • 疯狂的错误消息 – 但这是你的C ++

更新

在这里,为了您的方便,您的types安全枚举“库”。 粘贴这个标题:

 #ifndef _TYPESAFE_ENUMS_H #define _TYPESAFE_ENUMS_H #include <string> #include <boost/variant.hpp> #define ITEM(NAME, VAL) \ struct NAME##_t { \ std::string toStr() const { return std::string( #NAME ); } \ int toInt() const { return VAL; } \ }; \ static const NAME##_t NAME = NAME##_t(); \ template <typename T> \ bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \ class toStr_visitor: public boost::static_visitor<std::string> { public: template<typename T> std::string operator()(const T & a) const { return a.toStr(); } }; template<BOOST_VARIANT_ENUM_PARAMS(typename T)> inline static std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) { return boost::apply_visitor(toStr_visitor(), a); } class toInt_visitor: public boost::static_visitor<int> { public: template<typename T> int operator()(const T & a) const { return a.toInt(); } }; template<BOOST_VARIANT_ENUM_PARAMS(typename T)> inline static int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) { return boost::apply_visitor(toInt_visitor(), a); } #define ENUM(...) \ typedef boost::variant<__VA_ARGS__> #endif 

并使用它:

 ITEM(A, 0); ITEM(B, 1); ITEM(C, 2); ENUM(A_t, B_t) AB; ENUM(B_t, C_t) BC; 

注意你必须在ENUMmacros中说A_t而不是A ,这会破坏一些魔法。 好吧。 此外,请注意,现在有一个toStr函数和一个toInt函数来满足OP对简单转换为string和整数的要求。 我不明白的要求是迭代项目的一种方法。 让我知道如果你知道如何写这样的事情。

不知道这篇文章是不是太晚了,但有一篇关于GameDev.net的文章,它满足了第5点(遍历枚举器的能力): http : //www.gamedev.net/reference/snippets/features/cppstringizing/

本文描述的方法允许string转换支持现有的枚举而不改变它们的代码。 如果你只想要新的枚举支持,我会用Boost.Enum(上面提到的)去。