一流的 C++ 枚举






4.57/5 (7投票s)
一流的 C++ 枚举
引言
枚举是一组常量整数值,各种编程语言都支持此功能。在深入研究枚举的概念之前,我想简要回顾一下整数在编程中通常是如何使用的,因为整数是枚举的基础。我们通常将数字视为数量的表示。其他数字本身就具有意义。例如
数字 | 含义 |
---|---|
1492 | 哥伦布航行到美洲的年份。也有证据表明,在同一年发明了 COBOL。由于当时还没有计算机,因此在 1500 年没有出现 Y1.5K 问题。 |
5150 | Van Halen 专辑的名称,该专辑以 Eddie Van Halen 的家庭录音室命名。该录音室以加利福尼亚州一项与心理健康相关的法律命名。 |
32 | 淡水结冰的华氏温度。顺便说一句,Daniel Fahrenheit 选择盐水的冰点作为零温度,这使得该温度刻度成为与我们超级直观的美国重量和度量衡系统一起使用的自然选择。 |
2012 | 玛雅历结束的年份。在那一年,考古学家希望通过证明日历上的日期有一个缺失的数字来拯救世界,但事与愿违,却发现是一个前导零。真倒霉…… |
这些数字值都具有数学以外的意义。在编程中,数字值通常用于标识某物——数据库表中的一行、错误条件等等。同样,这些数字存在的数学目的并不高,尽管它们可能是按位编码或定义在指示错误严重性的范围内。整数可以毫不含糊地进行比较,不像文本,而且它们的体积小(通常为 32 或 64 位),易于按值传递。所有开发人员都直观地知道这一点,但我们很少考虑我们如何使用数字来指示状态或结果。表示代码中状态的数字对读者来说几乎没有明显的意义,因此我们使用枚举在代码中放入友好的标识符。这些文本标识符在编译时会被替换为常量整数,尽管 enum
s 也有自己的数据类型。例如,我们将定义一个扑克牌枚举,然后编写一个函数来随机选择一张牌。首先,enum
enum PlayingCard {
Joker = 0,
AceOfClubs = 1,
Num2OfClubs = 2,
Num3OfClubs = 3,
Num4OfClubs = 4,
Num5OfClubs = 5,
Num6OfClubs = 6,
Num7OfClubs = 7,
Num8OfClubs = 8,
Num9OfClubs = 9,
Num10OfClubs = 10,
JackOfClubs = 11,
QueenOfClubs = 12,
KingOfClubs = 13,
AceOfDiamonds = 14,
Num2OfDiamonds = 15,
Num3OfDiamonds = 16,
Num4OfDiamonds = 17,
Num5OfDiamonds = 18,
Num6OfDiamonds = 19,
Num7OfDiamonds = 20,
Num8OfDiamonds = 21,
Num9OfDiamonds = 22,
Num10OfDiamonds = 23,
JackOfDiamonds = 24,
QueenOfDiamonds = 25,
KingOfDiamonds = 26,
AceOfHearts = 27,
Num2OfHearts = 28,
Num3OfHearts = 29,
Num4OfHearts = 30,
Num5OfHearts = 31,
Num6OfHearts = 32,
Num7OfHearts = 33,
Num8OfHearts = 34,
Num9OfHearts = 35,
Num10OfHearts = 36,
JackOfHearts = 37,
QueenOfHearts = 38,
KingOfHearts = 39,
AceOfSpades = 40,
Num2OfSpades = 41,
Num3OfSpades = 42,
Num4OfSpades = 43,
Num5OfSpades = 44,
Num6OfSpades = 45,
Num7OfSpades = 46,
Num8OfSpades = 47,
Num9OfSpades = 48,
Num10OfSpades = 49,
JackOfSpades = 50,
QueenOfSpades = 51,
KingOfSpades = 52,
Modulus
};
如您所知,enum
为每张牌面提供了一个有意义的标识符,该标识符映射到一个整数值。这是一个利用枚举、随机选择一副牌然后将其打印出来的简短代码片段。我们期望在标准输出控制台中看到有意义的输出。
static PlayingCard randomCard();
int _tmain(int argc, _TCHAR* argv[])
{
srand( (unsigned int) Joker );
PlayingCard card = randomCard();
std::cout << "Random card: " << card << std::endl;
#ifdef _DEBUG
std::cout << "Press <Enter> to continue: ";
getchar();
#endif
return 0;
}
static PlayingCard randomCard()
{
return PlayingCard( (PlayingCard) (rand() % Modulus) );
}
控制台输出是……
Random card: 25
什么?25?看起来 enum
s 在 C++ 中是二等公民。这些友好的名称仅在代码内部可见。这就像你去更新驾照一样——你有名字,但你取了个号码。例如,你不知道 97 号有什么意义,直到叫到这个号码,这个人站起来。用于标识的数字需要某种上下文来传达含义。
让我们总结一下我们在 C++ 中面临的挑战。我们程序员为整数赋予意义,表示我们软件的内部状态。我们使用枚举(或预处理器定义、静态常量)来提高代码的可读性。当我们的代码出现问题时,我们需要发出输出进行故障排除。不幸的是,当将错误值转储到调试监视器或控制台时,原始数字没有上下文意义,迫使我们在即使问题是由于外部因素(例如,数据库或网络中断)引起时也必须查看代码。通常,非程序员完全能够解决这类问题,但他们必须打扰开发人员或滚动浏览文档才能弄清楚某个错误号码的含义。我们需要知道如何将自身的值作为有意义的文本发出的枚举。我们需要的是 C++ 的一等枚举。
背景
同样,我们希望 enum
s 发出有意义的文本输出。此外,我们希望它们支持某些数学运算符;例如,许多枚举旨在进行按位操作。我们希望它们具有有限的可能值集。枚举类型应支持反射,包括常量的迭代。这些类型应严格区分与其他类型,如果我们意外地尝试将它们的值设置为另一种数据类型的值,则会导致编译器报错。最后,如果枚举值表示错误或异常状态,为什么不使其可以像异常一样抛出呢?
C++ 中的解决方案可以手工编写,但每个枚举都需要大量的代码。很明显,我们需要一种工具或策略来自动化解决方案。满足上述所有要求的策略需要多次遍历枚举中的值。换句话说,我说的 enum { a, b, c }
生成的输出是 { t
1
(a), t
1
(b), t
1
(c) }, t
2
(a), t
2
(b), t
2
(c) } ... {t
n
(a), t
n
(b), t
n
(c)}
,其中每个 t
代表对枚举常量的单次转换。C++ 模板不提供优雅的解决方案。它们允许对参数进行多次转换,但模板参数的数量是固定的。预处理器可用于在编译时“循环”(参见 Boost 预处理器库),但操作预处理器需要奇怪的语法,甚至会使 IntelliSense 感到困惑。尽管如此,能够手工编写单个原型是概念验证,并且可以通过代码生成实现通用解决方案。
代码生成(又名元编程)听起来很激进,但事实并非如此。您在不考虑它的情况下一直使用代码生成。您的开发 IDE 中的向导会生成各种语言的代码。IDL(接口定义语言)编译器会生成代码。Visual Studio .NET 项目中的各种设计器工具会生成代码。
执行我们所需代码生成的方法有很多,但在所有情况下,您都会使用某种类型的解析器来读取您的枚举定义,并输出 C++ 头文件。解析是一项繁琐且容易出错的工作,因此最好将这项工作交给现有工具,而不是编写自定义的东西。您可以使用 XML,但 XML 非常冗长,会耗尽解决方案的所有优雅性。您可以使用 Google 的Protocol Buffers 库。它将挑战 PB 语言语法的极限,但这是可行的。
最终,我选择了最极客的解决方案。我使用了 Google 的V8 JavaScript 引擎,以便我可以使用 JSON(JavaScript 对象表示法)来指定 enum
定义。JSON 的优点在于它既是声明性的又是动态的。在声明性方面,它允许您定义一种类似于 C 结构的数据类型,它也可以看起来像 enum
定义。此外,JSON 类型可以通过编程方式进行操作,并且其数据结构在某种程度上类似于嵌套哈希表的树。由于是动态的,JSON 允许程序员实际编写一个程序来生成 enum
定义。您可以编写代码以循环方式生成我们的扑克牌,而不是声明性地生成。这使您能够通过简单的代码修改轻松地重新格式化所有常量名称,并消除了由于拼写错误引起的命名标准不一致。用于生成扑克牌枚举的 JavaScript 代码在示例应用程序中。
工作原理
免责声明:这不是 V8 教程。确实,世界上缺乏嵌入 V8 的示例,您可以选择使用该代码作为参考。V8 的专家都在一家名为Google的公司工作,我不在其中。我的代码可以运行 V8,但它可能不是教科书式的实现。
C++ 生成器由这个 V8 JavaScript 托管环境和一个非常小的自定义 JavaScript API 组成。用户编写一小块 JSON,然后告诉 cg
(API 中的代码生成器全局变量)获取信息并将 C++ 输出转储到文件中。jsmeta(JavaScript MetaCoder 的缩写)应用程序将 cg
暴露给脚本环境,赋予它几个有用的方法,并在其自己的 C++ 代码的深处完成所有繁重的工作。在jsmeta内部,JSON 对象会被扩展成一个更大的 C++ 代码块,以支持我们想要的功能。
生成的 C++ 代码中的大部分繁重工作都假定在您可以 #include
的头文件中实现。这是故意的。首先,它减少了代码的整体大小。其次,它允许开发人员自定义生成的 C++ 类下面的内容。cg
对象允许您将任何您想要的 文件 #include
到您的生成代码中。例如
cg.write('#include "enumerations/api.h"');
文件 enumerations/api.h 包含在源代码中。由生成的 C++ 类使用的模板和基类在此头文件中定义。此头文件将运算符 ++
和 --
实现为迭代器,而不是加/减固定运算符。换句话说,++
将值更改为枚举常量中的下一个,而不是加一。这解决了值限制要求,即值必须保留在枚举常量集合内。如前所述,您可以创建自己的 API 并 #include
它。这样做,您可以自由地定义运算符以具有不同的行为。
说到运算符,您会注意到 api.h 中的代码大量使用了运算符重载,尤其是“()
”函数对象运算符。运算符重载使用奇怪的语法,并且重载声明有点难读。在这种情况下使用函数对象有一个非常好的理由:我们不希望我们的基类与子类中定义的枚举值发生名称冲突。运算符是在 C++ 中没有名称的函数,这使我们能够避免保留枚举可能需要的名称。
Using the Code
如果您想构建 jsmeta 工具,您需要下载或构建 V8 库文件 v8.lib 和 v8_g.lib。您很可能需要从 Google 源代码构建它们。这些文件应复制到 jsmeta 项目中的 v8 文件夹。
附件中的程序已准备好作为工具安装在 MSVC 中。该过程非常简单,请遵循以下步骤
- 将规则文件和 EXE 文件(来自附件的 zip)放入一个文件夹,最好是 MSVC 的 EXE 搜索路径中的一个文件夹。图 1 显示了如何在 MSVC 中将文件夹添加到 EXE 搜索路径。
- 将 EXE 添加为生成工具。这需要以下步骤。
- 在解决方案资源管理器中右键单击您的项目,然后选择自定义生成工具。
- 按“查找现有”按钮并选择 jsmeta.rules。UI 如图 2 所示。
- 确保 JavaScript C++ MetaCoder 已打开。
- 向您的项目添加一个 JS 文件。
以下代码片段是一个定义枚举的 JavaScript / JSON 代码示例。请注意,其中两个枚举是动态使用代码定义的。
//test.js
//Add a #include at the top of the HPP output file.
cg.write('#include "enumerations/api.h"');
//Before defining namespaces and enums, here's a demo of JSON field behavior!
var jasonExample = {
a: 1,
a: 2 //Use the same field name, or from a hashtable perspective, the same KEY
};
//Dump the output to see what happens to the field 'a'.
cg.write("//jsonExample.a = ", jasonExample.a );
/* IMPORTANT NOTE:
In the test.js.hpp file generated by jsmeta, you'll see that jsonExample.a = 2.
This relates to the use of the terms Namespace, Enum, and BitEnum below.
If you define two Enum fields sequentially in a JSON block,
the second field will be used, and the information in the first
will be lost. Therefore, jsmeta allows you to disambiguate these fields
by planting _# on the end of the Enum, BitEnum, or NameSpace,
where # represents any number.
*/
//Define a namespace with enumerations. Namespaces may be nested.
var ns =
{ Namespace: { enumsamples: {
Enum: { SequentialEnum: {
FirstConstant: 0,
SecondConstant: {
Value: 2,
Throwable: true
},
NegativeValue: -41,
}},
//The _2 on the end is disregarded by jsmeta.
//This just tells JavaScript to consider this
//another JSON field. Naming this field Enum would overwrite the SampleEnum
//definition above.
Enum_2: { AnotherSequentialEnum: {
a : null, //Let the program auto-number this one.
b : null,
c : null,
d : 0,
}},
BitEnum: { SampleBitwiseEnum: {
Test9: 9,
Test1: {
Value: 1,
Throwable: true
},
Test2: 2,
Test8: 8,
TestAuto: null, //Should be 16
FirstMatch: 8,
NextMatch: 8,
}},
//Illustrates a nested namespace
Namespace: { printing: {
//Define an enumeration mapping paper sizes
//to their area in square tenths/mm.
//This is just a placeholder for the enum we'll generate below.
Enum_2: { PaperSizes : {
Invalid: 0,
}}
}},
Enum_3 : { CardDeck: {
Joker : 0
}}
}}};
//Advanced usage: Programmatically populate a 'PaperSizes' enumeration. The idea
//here is that every paper size maps to a single integer value, its area in square
//tenths of millimeters. If we can declare the members as shown below, it's easier
//to inspect each definition visually than if we pre-calculated the areas first. So
//they are declared as follows, then the sheetsizes JSON object will be transformed
//into the PaperSizes definition started above.
var sheetsizes = [
{name: 'Letter', width: 8.5, height: 11.0, metric: false},
{name: 'A4', width: 210, height: 297, metric: true}
];
//Iterate each sheet size definition.
for( var n=0; n<sheetsizes.length; n++ ){
var ss = sheetsizes[n];
var w = ss.width;
var h = ss.height;
//Convert to tenths of millimeters. This ensures all w and h values will
//be whole numbers.
if( ss.metric )
{
w *= 10.0;
h *= 10.0;
}
else {
w *= 254.0;
h *= 254.0;
}
//Add the calculated enum value to the PaperSizes enumeration.
ns.Namespace.enumsamples.Namespace.printing.Enum_2.PaperSizes[ss.name] =
Math.round(w * h);
}
//Example of populating a long array by looping.
//Let's map each card in a deck from 1 to 52, recalling
//that Joker = 0 in CardDeck declaration above.
var suits = Array("Clubs", "Diamonds", "Hearts", "Spades");
var names = {
1 : "Ace",
11 : "Jack",
12 : "Queen",
13 : "King",
};
//For each suit
for(var n=0; n<4; n++)
{
//For each face value in the suit
for( var faceval=1; faceval <= 13; ++faceval )
{
//See if we have a name mapped to the card's numeric value...
var name = names[faceval];
if( typeof(name) != 'string' ){
name = "Num" + faceval; //Identifiers can NOT start
//with a digit in C/C++, so start with 'Num'.
}
//Burn the name of the constant.
var cardname = name + "Of" + suits[n];
//Auto-number the constant.
ns.Namespace.enumsamples.Enum_3.CardDeck[cardname] = null;
}
}
//The next value gives us the total number of cards.
ns.Namespace.enumsamples.Enum_3.CardDeck['TotalCards'] = null;
//This rips the namespaces and enumerations in the JSON variable ns,
//writing output to test.js.hpp.
cg.write(ns);
您现在可以生成了。关于上面代码块中的 JSON 对象,有几点需要注意
- 命名空间是可选的。
- 命名空间可以嵌套在命名空间内。
- 请阅读上面的代码注释,标题为
重要说明
。这解释了奇怪的语法,例如Enum_2
。
上面的脚本生成以下 C++ 代码。生成的文件是 scriptfile.js.hpp,其中 scriptfile 是 JavaScript 文件名部分。
///test.js.hpp
#include "enumerations/api.h"
//jsonExample.a = 2
namespace enumsamples {
template<class TOuter> class SequentialEnum
: public enumerations::Enumeration,
public enumerations::MathOps<SequentialEnum<TOuter>>
{
friend class enumerations::MathOps<SequentialEnum<TOuter>>;
friend class enumerations::TEnumOps<SequentialEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
SequentialEnum() {}
enum enum_t {
FirstConstant = 0,
SecondConstant = 2,
NegativeValue = -41,
};
SequentialEnum(const SequentialEnum& other)
: Enumeration(other)
{
}
//Reflection implementation forSequentialEnum
class reflector_t : public enumerations::EnumReflector<
SequentialEnum<TOuter>, 3, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<SequentialEnum<TOuter>, 3>;
friend class SequentialEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"FirstConstant", (int_t)0, /*next match offset*/0, 0},
{"SecondConstant", (int_t)2, /*next match offset*/0, 1},
{"NegativeValue", (int_t)-41, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static PMETADATA metadata(){
static METADATA md = {"SequentialEnum", entries(), 3};
return &md;
}
static enum_t first(){
return SequentialEnum<TOuter>::FirstConstant;
}
static enum_t last(){
return SequentialEnum<TOuter>::NegativeValue;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,},1},
{{defs + 2/* -41 */,},1},
{{defs + 1/* 2 */,},1},
{{},0},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 3;
}
typedef struct _hashnode {
PENTRY ents[1];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t
operator enum_t() const { return
(enum_t)reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
SequentialEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
SequentialEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<SequentialEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 3 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
operator PMETADATA() const { return reflector_t::metadata(); }
};
template<class TOuter> class AnotherSequentialEnum
: public enumerations::Enumeration,
public enumerations::MathOps<AnotherSequentialEnum<TOuter>>
{
friend class enumerations::MathOps<AnotherSequentialEnum<TOuter>>;
friend class enumerations::TEnumOps<AnotherSequentialEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
AnotherSequentialEnum() {}
enum enum_t {
a = 0,
b = 1,
c = 2,
d = 0,
};
AnotherSequentialEnum(const AnotherSequentialEnum& other)
: Enumeration(other)
{
}
//Reflection implementation forAnotherSequentialEnum
class reflector_t : public enumerations::EnumReflector<
AnotherSequentialEnum<TOuter>, 4, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<AnotherSequentialEnum<TOuter>, 4>;
friend class AnotherSequentialEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"a", (int_t)0, /*next match offset*/3, 0},
{"b", (int_t)1, /*next match offset*/0, 0},
{"c", (int_t)2, /*next match offset*/0, 0},
{"d", (int_t)0, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static PMETADATA metadata(){
static METADATA md =
{"AnotherSequentialEnum", entries(), 4};
return &md;
}
static enum_t first(){
return AnotherSequentialEnum<TOuter>::a;
}
static enum_t last(){
return AnotherSequentialEnum<TOuter>::d;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 3/* 0 */,},2},
{{defs + 1/* 1 */,},1},
{{defs + 2/* 2 */,},1},
{{},0},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 4;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t
operator enum_t() const { return (enum_t)reflector_t::entries()
[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
AnotherSequentialEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
AnotherSequentialEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<AnotherSequentialEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 4 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
operator PMETADATA() const { return reflector_t::metadata(); }
};
template<class TOuter> class SampleBitwiseEnum
: public enumerations::Enumeration,
public enumerations::BitwiseOps<SampleBitwiseEnum<TOuter>>
{
friend class enumerations::BitwiseOps<SampleBitwiseEnum<TOuter>>;
friend class enumerations::TEnumOps<SampleBitwiseEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
SampleBitwiseEnum() {}
enum enum_t {
Bitsum_0x0 = 0,
Test1 = 1,
Test2 = 2,
Bitsum_0x3 = 3,
Test8 = 8,
Test9 = 9,
Bitsum_0xa = 10,
Bitsum_0xb = 11,
TestAuto = 16,
Bitsum_0x11 = 17,
Bitsum_0x12 = 18,
Bitsum_0x13 = 19,
Bitsum_0x18 = 24,
Bitsum_0x19 = 25,
Bitsum_0x1a = 26,
Bitsum_0x1b = 27,
FirstMatch = 8,
NextMatch = 8,
};
SampleBitwiseEnum(const SampleBitwiseEnum& other)
: Enumeration(other)
{
}
//Reflection implementation forSampleBitwiseEnum
class reflector_t : public enumerations::EnumReflector<
SampleBitwiseEnum<TOuter>, 18, BITWISE_ENUM> {
friend class enumerations::BaseEnumReflector
<SampleBitwiseEnum<TOuter>, 18>;
friend class SampleBitwiseEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Bitsum_0x0", (int_t)0, /*next match offset*/0, 4},
{"Test1", (int_t)1, /*next match offset*/0, 1},
{"Test2", (int_t)2, /*next match offset*/0, 0},
{"Bitsum_0x3", (int_t)3, /*next match offset*/0, 5},
{"Test8", (int_t)8, /*next match offset*/12, 0},
{"Test9", (int_t)9, /*next match offset*/0, 0},
{"Bitsum_0xa", (int_t)10, /*next match offset*/0, 4},
{"Bitsum_0xb", (int_t)11, /*next match offset*/0, 5},
{"TestAuto", (int_t)16, /*next match offset*/0, 0},
{"Bitsum_0x11", (int_t)17, /*next match offset*/0, 5},
{"Bitsum_0x12", (int_t)18, /*next match offset*/0, 4},
{"Bitsum_0x13", (int_t)19, /*next match offset*/0, 5},
{"Bitsum_0x18", (int_t)24, /*next match offset*/0, 4},
{"Bitsum_0x19", (int_t)25, /*next match offset*/0, 5},
{"Bitsum_0x1a", (int_t)26, /*next match offset*/0, 4},
{"Bitsum_0x1b", (int_t)27, /*next match offset*/0, 5},
{"FirstMatch", (int_t)8, /*next match offset*/1, 0},
{"NextMatch", (int_t)8, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static PMETADATA metadata(){
static METADATA md = {"SampleBitwiseEnum", entries(), 18};
return &md;
}
static enum_t first(){
return SampleBitwiseEnum<TOuter>::Bitsum_0x0;
}
static enum_t last(){
return SampleBitwiseEnum<TOuter>::NextMatch;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 14/* 26 */,},2},
{{defs + 1/* 1 */,defs + 15/* 27 */,},2},
{{defs + 2/* 2 */,},1},
{{defs + 3/* 3 */,defs + 8/* 16 */,},2},
{{defs + 9/* 17 */,},1},
{{defs + 10/* 18 */,},1},
{{defs + 11/* 19 */,},1},
{{},0},
{{defs + 4/* 8 */,defs + 16/* 8 */,
defs + 17/* 8 */,},3},
{{defs + 5/* 9 */,},1},
{{defs + 6/* 10 */,},1},
{{defs + 7/* 11 */,defs + 12/* 24 */,},2},
{{defs + 13/* 25 */,},1},
};
HASHNODE& node = nodes[positiveModulus(val, 13)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 18;
}
typedef struct _hashnode {
PENTRY ents[3];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t
operator enum_t() const { return (enum_t)
reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
SampleBitwiseEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
SampleBitwiseEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<SampleBitwiseEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 18 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
operator PMETADATA() const { return reflector_t::metadata(); }
};
namespace printing {
template<class TOuter> class PaperSizes
: public enumerations::Enumeration,
public enumerations::MathOps<PaperSizes<TOuter>>
{
friend class enumerations::MathOps<PaperSizes<TOuter>>;
friend class enumerations::TEnumOps<PaperSizes>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
PaperSizes() {}
enum enum_t {
Invalid = 0,
Letter = 6032246,
A4 = 6237000,
};
PaperSizes(const PaperSizes& other)
: Enumeration(other)
{
}
//Reflection implementation forPaperSizes
class reflector_t : public enumerations::EnumReflector<
PaperSizes<TOuter>, 3, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<PaperSizes<TOuter>, 3>;
friend class PaperSizes;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Invalid", (int_t)0, /*next match offset*/0, 0},
{"Letter", (int_t)6032246, /*next match offset*/0, 0},
{"A4", (int_t)6237000, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static PMETADATA metadata(){
static METADATA md = {"PaperSizes", entries(), 3};
return &md;
}
static enum_t first(){
return PaperSizes<TOuter>::Invalid;
}
static enum_t last(){
return PaperSizes<TOuter>::A4;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 2/* 6237000 */,},2},
{{},0},
{{},0},
{{defs + 1/* 6032246 */,},1},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 3;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t
operator enum_t() const { return (enum_t)
reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
PaperSizes(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
PaperSizes& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<PaperSizes<TOuter>> Exception;
void tryThrowException(const std::string&
msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 3 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
operator PMETADATA() const { return reflector_t::metadata(); }
};
}; // end namespace printing
template<class TOuter> class CardDeck
: public enumerations::Enumeration,
public enumerations::MathOps<CardDeck<TOuter>>
{
friend class enumerations::MathOps<CardDeck<TOuter>>;
friend class enumerations::TEnumOps<CardDeck>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
CardDeck() {}
enum enum_t {
Joker = 0,
AceOfClubs = 1,
Num2OfClubs = 2,
Num3OfClubs = 3,
Num4OfClubs = 4,
Num5OfClubs = 5,
Num6OfClubs = 6,
Num7OfClubs = 7,
Num8OfClubs = 8,
Num9OfClubs = 9,
Num10OfClubs = 10,
JackOfClubs = 11,
QueenOfClubs = 12,
KingOfClubs = 13,
AceOfDiamonds = 14,
Num2OfDiamonds = 15,
Num3OfDiamonds = 16,
Num4OfDiamonds = 17,
Num5OfDiamonds = 18,
Num6OfDiamonds = 19,
Num7OfDiamonds = 20,
Num8OfDiamonds = 21,
Num9OfDiamonds = 22,
Num10OfDiamonds = 23,
JackOfDiamonds = 24,
QueenOfDiamonds = 25,
KingOfDiamonds = 26,
AceOfHearts = 27,
Num2OfHearts = 28,
Num3OfHearts = 29,
Num4OfHearts = 30,
Num5OfHearts = 31,
Num6OfHearts = 32,
Num7OfHearts = 33,
Num8OfHearts = 34,
Num9OfHearts = 35,
Num10OfHearts = 36,
JackOfHearts = 37,
QueenOfHearts = 38,
KingOfHearts = 39,
AceOfSpades = 40,
Num2OfSpades = 41,
Num3OfSpades = 42,
Num4OfSpades = 43,
Num5OfSpades = 44,
Num6OfSpades = 45,
Num7OfSpades = 46,
Num8OfSpades = 47,
Num9OfSpades = 48,
Num10OfSpades = 49,
JackOfSpades = 50,
QueenOfSpades = 51,
KingOfSpades = 52,
TotalCards = 53,
};
CardDeck(const CardDeck& other)
: Enumeration(other)
{
}
//Reflection implementation forCardDeck
class reflector_t : public enumerations::EnumReflector<CardDeck<TOuter>,
54, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector<CardDeck<TOuter>, 54>;
friend class CardDeck;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Joker", (int_t)0, /*next match offset*/0, 0},
{"AceOfClubs", (int_t)1, /*next match offset*/0, 0},
{"Num2OfClubs", (int_t)2, /*next match offset*/0, 0},
{"Num3OfClubs", (int_t)3, /*next match offset*/0, 0},
{"Num4OfClubs", (int_t)4, /*next match offset*/0, 0},
{"Num5OfClubs", (int_t)5, /*next match offset*/0, 0},
{"Num6OfClubs", (int_t)6, /*next match offset*/0, 0},
{"Num7OfClubs", (int_t)7, /*next match offset*/0, 0},
{"Num8OfClubs", (int_t)8, /*next match offset*/0, 0},
{"Num9OfClubs", (int_t)9, /*next match offset*/0, 0},
{"Num10OfClubs", (int_t)10, /*next match offset*/0, 0},
{"JackOfClubs", (int_t)11, /*next match offset*/0, 0},
{"QueenOfClubs", (int_t)12, /*next match offset*/0, 0},
{"KingOfClubs", (int_t)13, /*next match offset*/0, 0},
{"AceOfDiamonds", (int_t)14, /*next match offset*/0, 0},
{"Num2OfDiamonds", (int_t)15, /*next match offset*/0, 0},
{"Num3OfDiamonds", (int_t)16, /*next match offset*/0, 0},
{"Num4OfDiamonds", (int_t)17, /*next match offset*/0, 0},
{"Num5OfDiamonds", (int_t)18, /*next match offset*/0, 0},
{"Num6OfDiamonds", (int_t)19, /*next match offset*/0, 0},
{"Num7OfDiamonds", (int_t)20, /*next match offset*/0, 0},
{"Num8OfDiamonds", (int_t)21, /*next match offset*/0, 0},
{"Num9OfDiamonds", (int_t)22, /*next match offset*/0, 0},
{"Num10OfDiamonds", (int_t)23, /*next match offset*/0, 0},
{"JackOfDiamonds", (int_t)24, /*next match offset*/0, 0},
{"QueenOfDiamonds", (int_t)25, /*next match offset*/0, 0},
{"KingOfDiamonds", (int_t)26, /*next match offset*/0, 0},
{"AceOfHearts", (int_t)27, /*next match offset*/0, 0},
{"Num2OfHearts", (int_t)28, /*next match offset*/0, 0},
{"Num3OfHearts", (int_t)29, /*next match offset*/0, 0},
{"Num4OfHearts", (int_t)30, /*next match offset*/0, 0},
{"Num5OfHearts", (int_t)31, /*next match offset*/0, 0},
{"Num6OfHearts", (int_t)32, /*next match offset*/0, 0},
{"Num7OfHearts", (int_t)33, /*next match offset*/0, 0},
{"Num8OfHearts", (int_t)34, /*next match offset*/0, 0},
{"Num9OfHearts", (int_t)35, /*next match offset*/0, 0},
{"Num10OfHearts", (int_t)36, /*next match offset*/0, 0},
{"JackOfHearts", (int_t)37, /*next match offset*/0, 0},
{"QueenOfHearts", (int_t)38, /*next match offset*/0, 0},
{"KingOfHearts", (int_t)39, /*next match offset*/0, 0},
{"AceOfSpades", (int_t)40, /*next match offset*/0, 0},
{"Num2OfSpades", (int_t)41, /*next match offset*/0, 0},
{"Num3OfSpades", (int_t)42, /*next match offset*/0, 0},
{"Num4OfSpades", (int_t)43, /*next match offset*/0, 0},
{"Num5OfSpades", (int_t)44, /*next match offset*/0, 0},
{"Num6OfSpades", (int_t)45, /*next match offset*/0, 0},
{"Num7OfSpades", (int_t)46, /*next match offset*/0, 0},
{"Num8OfSpades", (int_t)47, /*next match offset*/0, 0},
{"Num9OfSpades", (int_t)48, /*next match offset*/0, 0},
{"Num10OfSpades", (int_t)49, /*next match offset*/0, 0},
{"JackOfSpades", (int_t)50, /*next match offset*/0, 0},
{"QueenOfSpades", (int_t)51, /*next match offset*/0, 0},
{"KingOfSpades", (int_t)52, /*next match offset*/0, 0},
{"TotalCards", (int_t)53, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static PMETADATA metadata(){
static METADATA md = {"CardDeck", entries(), 54};
return &md;
}
static enum_t first(){
return CardDeck<TOuter>::Joker;
}
static enum_t last(){
return CardDeck<TOuter>::TotalCards;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 53/* 53 */,},2},
{{defs + 1/* 1 */,},1},
{{defs + 2/* 2 */,},1},
{{defs + 3/* 3 */,},1},
{{defs + 4/* 4 */,},1},
{{defs + 5/* 5 */,},1},
{{defs + 6/* 6 */,},1},
{{defs + 7/* 7 */,},1},
{{defs + 8/* 8 */,},1},
{{defs + 9/* 9 */,},1},
{{defs + 10/* 10 */,},1},
{{defs + 11/* 11 */,},1},
{{defs + 12/* 12 */,},1},
{{defs + 13/* 13 */,},1},
{{defs + 14/* 14 */,},1},
{{defs + 15/* 15 */,},1},
{{defs + 16/* 16 */,},1},
{{defs + 17/* 17 */,},1},
{{defs + 18/* 18 */,},1},
{{defs + 19/* 19 */,},1},
{{defs + 20/* 20 */,},1},
{{defs + 21/* 21 */,},1},
{{defs + 22/* 22 */,},1},
{{defs + 23/* 23 */,},1},
{{defs + 24/* 24 */,},1},
{{defs + 25/* 25 */,},1},
{{defs + 26/* 26 */,},1},
{{defs + 27/* 27 */,},1},
{{defs + 28/* 28 */,},1},
{{defs + 29/* 29 */,},1},
{{defs + 30/* 30 */,},1},
{{defs + 31/* 31 */,},1},
{{defs + 32/* 32 */,},1},
{{defs + 33/* 33 */,},1},
{{defs + 34/* 34 */,},1},
{{defs + 35/* 35 */,},1},
{{defs + 36/* 36 */,},1},
{{defs + 37/* 37 */,},1},
{{defs + 38/* 38 */,},1},
{{defs + 39/* 39 */,},1},
{{defs + 40/* 40 */,},1},
{{defs + 41/* 41 */,},1},
{{defs + 42/* 42 */,},1},
{{defs + 43/* 43 */,},1},
{{defs + 44/* 44 */,},1},
{{defs + 45/* 45 */,},1},
{{defs + 46/* 46 */,},1},
{{defs + 47/* 47 */,},1},
{{defs + 48/* 48 */,},1},
{{defs + 49/* 49 */,},1},
{{defs + 50/* 50 */,},1},
{{defs + 51/* 51 */,},1},
{{defs + 52/* 52 */,},1},
};
HASHNODE& node = nodes[positiveModulus(val, 53)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 54;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t
operator enum_t() const { return
(enum_t)reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
CardDeck(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
CardDeck& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<CardDeck<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 54 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
operator PMETADATA() const { return reflector_t::metadata(); }
};
}; // end namespace enumsamples
这里有很多需要理解的地方!主要观点如下
- 类实例大致相当于其包含枚举的单个值。该实例的当前值通过
enum_t
转换运算符检索。 - 该类是可变的。迭代器(
++
和--
)和复制(=
)运算符会更改实例的当前值。 - 唯一的成员变量来自
Enumeration
基类。因此,sizeof()
运算符返回一个非常小的量,并且您可以从函数返回这些类对象。 - 类实现了复制运算符和复制构造函数。
- 加法和减法固定运算符是从基类
Enumeration
继承的(++
和--
)。这两个运算符都是迭代的;它们将当前值更改为枚举中的下一个或上一个常量。不进行加法或减法。 - 当枚举常量是带有
Throwable: true
属性的 JSON 块时,tryThrowException()
函数将抛出enumerations::Exception
。 - 基类允许将任何
enumerations::Enumeration
对象转储到std::ostream
类实例,例如std::cout
。输出将显示值为string
而不是数字常量。这对于故障排除非常有价值。
以下代码演示了如何插入由 jsmeta 工具生成的模板类
class Foo
{
public:
typedef enumsamples::SequentialEnum<Foo> SequentialEnum;
typedef enumsamples::SampleBitwiseEnum<Foo> SampleBitwiseEnum;
typedef enumsamples::printing::PaperSizes<Foo> PaperSizes;
};
//You can define "global" enums outside a class. Just use <int> as the parameter type.
typedef enumsamples::CardDeck<int> PlayingCard;
此时需要指出一件事:生成的 enum
类都是模板。这允许基于模板参数进行类型隔离。如果您两次使用不同的模板参数 T
对 typedef SampleEnum<T> SampleEnum
进行类型定义,则结果类型不兼容。这是必需的,因为我们通常将外部类用作模板参数,并且一个类内部定义的 enum
通常应与另一个类内部的 enum
不兼容,即使两个枚举来自同一个模板。相反的情况也是可能的。如果您只使用 int
作为随机模板参数,您实际上可以在多个类内部多次 typedef
同一个枚举,并且结果类型将全部兼容。因此,模板化这些类使得两种不同的、互斥的使用场景成为可能。请参阅 testharness.cpp 以了解此概念的说明。
关注点
在插入代码片段时,我意识到我可以从相对少量的脚本代码中生成大量的 C++ 代码。所有这些都可以实现,而无需添加 C++ 标准不支持的关键字或其他专有功能。我并不是说这是一件坏事,但它需要大量的生成功能来支持 QT 或 Managed C++ 的专有语言扩展。支持一等枚举值只需要很少的生成功能,并且不依赖于对 C++ 标准的任何偏离。
我注意到的另一件事是,这个解决方案实际上存在两个级别的代码生成。大多数代码生成解决方案,如 IDL(接口定义语言),都是完全声明性的。使用 JSON,我们实际上能够动态填充声明块,从而为我们提供了两个软件层来构建枚举类的内容。第 1 层是jsmeta 工具,第 2 层是动态填充 JSON 对象的可选功能。
每枚硬币都有两面,生成代码会使您的二进制文件变大。当内存有限时,这可能有点过头。请注意,枚举的名称/元数据是 static
变量,始终占用二进制文件中的一块数据空间。一个非常有趣的场景是当软件用于控制机械时。在这种用例中,每行代码都需要测试覆盖率,并且必须记录结果。在涉及 machinist 安全依赖于可靠软件的情况下,必须谨慎使用代码生成。一等 enum
s 并不总是适用于 C++ 的每种用例!
让我们回到起点,一副扑克牌。枚举在 C++ 中处于不利地位,因为 enum
s 中的常量名称在代码编译时会被丢弃。使用标准方式的 C++,enum
常量编译成数字,这些数字会忘记它们映射的名称。幸运的是,C++ 拥有强大的语言功能,例如运算符重载,它允许我们生成类来充当功能丰富的枚举。在示例代码的扑克牌枚举中,我们现在可以打印出每张牌的命名常量,而不是仅仅打印其整数映射。使用此功能,现在可以轻松地看到手中何时有一张 A 和一张 10!
历史
- 版本 1.0,2010 年 10 月