enum
C++中的类型是相当基本的; 它基本上只是为标签创建了一堆编译时值(可能具有适当的范围enum class
).
将相关的编译时常量分组在一起非常有吸引力:
enum class Animal{
DOG,
CAT,
COW,
...
};
// ...
Animal myAnimal = Animal::DOG;
Run Code Online (Sandbox Code Playgroud)
然而,它有各种各样的缺点,包括:
在这篇文章中,我试图创建一种解决这些缺点的类型.
一个理想的解决方案采用常量的编译时知识及其与字符串的关联的概念,并将它们组合成一个类似于scoped-enum的对象,可以通过enum id和enum string name进行搜索.最后,结果类型将使用尽可能接近枚举语法的语法.
在这篇文章中,我将首先概述其他人为各个部分所尝试的内容,然后介绍两种方法,一种完成上述方法,但由于静态成员的初始化顺序而具有未定义的行为,另一种解决方案不那么漂亮语法但由于初始化顺序没有未定义的行为.
关于获取枚举中的项目数量(1 2 3)以及网上提出的大量其他问题(4 5 6)等,有很多问题.而且普遍的共识是,没有确定 -火的方式做到这一点.
仅当您强制执行枚举值为正且增加时,以下模式才有效:
enum Foo{A=0, B, C, D, FOOCOUNT}; // FOOCOUNT is 4
Run Code Online (Sandbox Code Playgroud)
但是,如果您尝试编码某种需要任意值的业务逻辑,则很容易被破坏:
enum Foo{A=-1, B=120, C=42, D=6, FOOCOUNT}; // ????
Run Code Online (Sandbox Code Playgroud)
所以Boost的开发人员试图用Boost.Enum来解决这个问题,Boost.Enum使用了一些相当复杂的宏来扩展到一些至少会给你大小的代码.
在迭代枚举中有一些尝试; 可以迭代的类似枚举的对象,理论上允许隐式大小计算,甚至在[7](7 8 9,...)的情况下明确允许
尝试实现这一点通常会导致自由浮动函数和使用宏来适当地调用它们.(8 9 10)
这也包括按字符串搜索枚举(13)
是的,这意味着没有Boost.Enum或类似方法
当你开始远离实际的枚举时,这是一个相当独特的问题;
当他们离开实际的枚举时,也会遇到一个问题.枚举列表被视为一个集合,用户希望查询它以获取特定的编译时已知值.(请参阅可迭代枚举和枚举到字符串转换)
在这一点上,很明显我们不能真正使用枚举了.但是,我仍然喜欢用户的类似enum的界面
让我们说我认为我非常聪明并且意识到如果我有一些课程A
:
struct A
{
static int myInt;
};
int A::myInt;
Run Code Online (Sandbox Code Playgroud)
然后我可以myInt
通过说A::myInt
.
这与我访问的方式相同enum
:
enum A{myInt};
// ...
// A::myInt
Run Code Online (Sandbox Code Playgroud)
我对自己说:嗯,我提前知道我所有的枚举值,所以enum基本上是这样的:
struct MyEnum
{
static const int A;
static const int B;
// ...
};
const int MyEnum::A = 0;
const int MyEnum::B = 1;
// ...
Run Code Online (Sandbox Code Playgroud)
接下来,我想要更高兴; 让我们解决我们需要std::string
和int
转换的约束:
struct EnumValue
{
EnumValue(std::string _name): name(std::move(_name)), id(gid){++gid;}
std::string name;
int id;
operator std::string() const
{
return name;
}
operator int() const
{
return id;
}
private:
static int gid;
};
int EnumValue::gid = 0;
Run Code Online (Sandbox Code Playgroud)
然后我可以用static
EnumValue
s 声明一些包含类:
class MyEnum
{
public:
static const EnumValue Alpha;
static const EnumValue Beta;
static const EnumValue Gamma;
};
const EnumValue MyEnum::Alpha = EnumValue("Alpha")
const EnumValue MyEnum::Beta = EnumValue("Beta")
const EnumValue MyEnum::Gamma = EnumValue("Gamma")
Run Code Online (Sandbox Code Playgroud)
大!这解决了我们的一些限制,但如何搜索集合?嗯,如果我们现在添加一个static
容器unordered_map
,那么事情会变得更酷!抛出一些#define
s来缓解字符串拼写错误:
#define ALPHA "Alpha"
#define BETA "Beta"
#define GAMMA "Gamma"
// ...
class MyEnum
{
public:
static const EnumValue& Alpha;
static const EnumValue& Beta;
static const EnumValue& Gamma;
static const EnumValue& StringToEnumeration(std::string _in)
{
return enumerations.find(_in)->second;
}
static const EnumValue& IDToEnumeration(int _id)
{
auto iter = std::find_if(enumerations.cbegin(), enumerations.cend(),
[_id](const map_value_type& vt)
{
return vt.second.id == _id;
});
return iter->second;
}
static const size_t size()
{
return enumerations.size();
}
private:
typedef std::unordered_map<std::string, EnumValue> map_type ;
typedef map_type::value_type map_value_type ;
static const map_type enumerations;
};
const std::unordered_map<std::string, EnumValue> MyEnum::enumerations =
{
{ALPHA, EnumValue(ALPHA)},
{BETA, EnumValue(BETA)},
{GAMMA, EnumValue(GAMMA)}
};
const EnumValue& MyEnum::Alpha = enumerations.find(ALPHA)->second;
const EnumValue& MyEnum::Beta = enumerations.find(BETA)->second;
const EnumValue& MyEnum::Gamma = enumerations.find(GAMMA)->second;
Run Code Online (Sandbox Code Playgroud)
现在我得到了通过name
或搜索枚举容器的额外好处id
:
std::cout << MyEnum::StringToEnumeration(ALPHA).id << std::endl; //should give 0
std::cout << MyEnum::IDToEnumeration(0).name << std::endl; //should give "Alpha"
Run Code Online (Sandbox Code Playgroud)
这一切都感觉非常错误.我们正在初始化大量静态数据.我的意思是,直到最近我们才能map
在编译时填充!(11)
然后是静态初始化命令fiasco的问题:
破坏程序的一种微妙方式.
静态初始化顺序fiasco是C++中一个非常微妙且常被误解的方面.不幸的是,它很难被发现 - 错误经常发生在main()开始之前.
简而言之,假设您有两个静态对象x和y,它们存在于单独的源文件中,比如x.cpp和y.cpp.进一步假设y对象的初始化(通常是y对象的构造函数)调用x对象上的某个方法.
而已.就这么简单.
悲剧的是你有50%-50%的死亡几率.如果x.cpp的编译单元首先被初始化,那么一切都很好.但是如果y.cpp的编译单元首先被初始化,那么y的初始化将在x初始化之前运行,并且你是吐司.例如,y的构造函数可以调用x对象上的方法,但尚未构造x对象.
我听说他们正在招聘麦当劳.享受翻新汉堡的新工作.
如果你觉得在半个房间里玩俄罗斯轮盘赌是"令人兴奋的",你可以在这里停止阅读.另一方面,如果您希望通过系统地预防灾难来提高生存机会,您可能希望阅读下一个常见问题解答.
注意:在某些情况下,静态初始化顺序fiasco也适用于内置/内部类型.
这可以通过初始化静态数据并返回它的getter函数进行调解(12):
Fred& GetFred()
{
static Fred* ans = new Fred();
return *ans;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我这样做,现在我必须调用一个函数来初始化我的静态数据,并且我失去了你在上面看到的漂亮语法!
#问题#所以,现在我终于解决了我的问题:
对这篇文章的评论似乎表明强烈偏好静态访问器函数来解决静态订单初始化问题:
public:
typedef std::unordered_map<std::string, EnumValue> map_type ;
typedef map_type::value_type map_value_type ;
static const map_type& Enumerations()
{
static map_type enumerations {
{ALPHA, EnumValue(ALPHA)},
{BETA, EnumValue(BETA)},
{GAMMA, EnumValue(GAMMA)}
};
return enumerations;
}
static const EnumValue& Alpha()
{
return Enumerations().find(ALPHA)->second;
}
static const EnumValue& Beta()
{
return Enumerations().find(BETA)->second;
}
static const EnumValue& Gamma()
{
return Enumerations().find(GAMMA)->second;
}
Run Code Online (Sandbox Code Playgroud)
我更新的问题如下:
有没有办法可能只使用访问器函数来初始化unordered_map
,但仍然(安全地)能够使用类似enum的语法访问"枚举"值?例如:
MyEnum::Enumerations()::Alpha
要么
MyEnum::Alpha
Run Code Online (Sandbox Code Playgroud)
而不是我现在拥有的:
MyEnum::Alpha()
Run Code Online (Sandbox Code Playgroud)
我相信这个问题的答案也将解决我在帖子中详细阐述的枚举问题(Enum是引号,因为结果类型不是枚举,但我们想要类似枚举的行为):
具体来说,如果我们可以做我已经做过的事情,但是在执行静态初始化命令时以某种方式实现类似于枚举的语法,我认为这是可以接受的
Bar*_*rry 16
有时,当您想要执行语言不支持的操作时,您应该在语言外部查看以支持它.在这种情况下,代码生成似乎是最好的选择.
从包含枚举的文件开始.我会完全随意选择XML,但实际上任何合理的格式都可以:
<enum name="MyEnum">
<item name="ALPHA" />
<item name="BETA" />
<item name="GAMMA" />
</enum>
Run Code Online (Sandbox Code Playgroud)
在那里添加你需要的任何可选字段都很容易(你需要一个value
吗?应该enum
是未编组的吗?有一个指定的类型?).
然后用你选择的语言编写一个代码生成器,将该文件转换为C++头文件(或头文件/源文件)la:
enum class MyEnum {
ALPHA,
BETA,
GAMMA,
};
std::string to_string(MyEnum e) {
switch (e) {
case MyEnum::ALPHA: return "ALPHA";
case MyEnum::BETA: return "BETA";
case MyEnum::GAMMA: return "GAMMA";
}
}
MyEnum to_enum(const std::string& s) {
static std::unordered_map<std::string, MyEnum> m{
{"ALPHA", MyEnum::ALPHA},
...
};
auto it = m.find(s);
if (it != m.end()) {
return it->second;
}
else {
/* up to you */
}
}
Run Code Online (Sandbox Code Playgroud)
代码生成方法的优点是可以轻松生成您想要的任何复杂的代码.基本上只是支持你目前遇到的所有问题.
归档时间: |
|
查看次数: |
1494 次 |
最近记录: |