枚举和初始化类
Enums and initialising classes
我经常遇到这种烦人的怪癖。
假设我有类似的东西
enum class Thing {Ex1, Ex2, Ex3}
我有一个Thing的实例,想根据它是什么创建一个特定的类,结果它看起来像这个
switch(_thing){
case Thing::Ex1:
ptr = new Class1();
break;
case Thing::Ex2:
ptr = new Class2();
break;
case Thing::Ex3:
ptr = new Class3();
break;
}
等等。有10-20个这样的声明,看起来很糟糕。这是不可避免的吗,还是有办法改善这一点?
不幸的是,C++仍然没有反射。如果你的代码中有多个地方需要使用相同的值到类型的映射,你可以考虑久经考验的X宏:
#define CASES
X(Thing::Ex1, Class1)
X(Thing::Ex2, Class2)
X(Thing::Ex3, Class3)
// ...
// ...
switch (_thing) {
#define X(v, T)
case (v):
ptr = new (T)();
break;
CASES
#undef X
}
// ... do other things with CASES and a different X macro
// ...
#undef CASES
C++中隐藏了一个代码生成器,名为Template Metaprogramming。你可以用它来生产你想要的工厂。您也可以使用Variadic模板(这是C++11的特性)。
我得到了以下代码(我有点懒,无法实现正确的"最后一个"专用createThing
,所以我使用void
作为终止符):
enum class Thing {Ex1, Ex2, Ex3};
class Class1 { virtual void f() {} };
class Class2 : public Class1 {};
class Class3 : public Class2 {};
template <Thing val_, typename Class_>
struct MPLFactoryPair {
static constexpr Thing val = val_;
using Class = Class_;
};
template<class head, typename... tail>
struct MPLFactory {
static Class1* createThing(Thing thing_) {
if(thing_ == head::val) {
return new typename head::Class();
}
return MPLFactory<tail...>::createThing(thing_);
}
};
template<typename last>
struct MPLFactory<last> {
static Class1* createThing(Thing thing_) {
return nullptr;
}
};
using ThingFactory =
MPLFactory<MPLFactoryPair<Thing::Ex1, Class1>,
MPLFactoryPair<Thing::Ex2, Class2>,
MPLFactoryPair<Thing::Ex3, Class3>,
void>;
它的主要问题是,您应该希望编译器能够优化createThing
中的尾部调用。GCC使用-O2:
<+20>: test %eax,%eax
<+22>: je 0x400a5e <main(int, char**)+238>
<+28>: cmp $0x1,%eax
<+31>: je 0x400a83 <main(int, char**)+275>
<+37>: cmp $0x2,%eax
<+40>: jne 0x400a77 <main(int, char**)+263>
所以它变得很简单。不确定它是否会生成跳转表。
这是一个测试代码:
int main(int argc, char* argv[]) {
volatile Thing thing = Thing::Ex2;
Class1* ptr = ThingFactory::createThing(thing);
std::cout << "ptr = " << ptr << std::endl
<< "<Class2>(ptr) = " << dynamic_cast<Class2*>(ptr) << std::endl
<< "<Class3>(ptr) = " << dynamic_cast<Class3*>(ptr) << std::endl
<< std::endl;
}
对我来说,它输出:
ptr = 0x214c010
<Class2>(ptr) = 0x214c010
<Class3>(ptr) = 0
您也可以使用boost::mpl,但这并不容易。
如果项的数量变大,那么在"Things"和工厂方法之间创建一个关联(也称为map
)是有意义的。这在Python中很容易,但在C++中可能有点麻烦。
typedef Baseclass * (*factoryMethod) (void);
// factories contains the association between types and constructors
std::map <std::string, factoryMethod> factories;
// If possible, it's often a good idea to make these methods static members of Baseclass.
Baseclass * factoryMethodA(void)
{
return new ClassA();
}
Baseclass * factoryMethodB(void)
{
return new ClassB();
}
// &ct
my_map["A"] = factoryMethodA;
my_map["B"] = factoryMethodB;
// &ct
而用法只是。。。
ptr = factories[classname]();
如果你使用的是C++11,我认为你可以用lambdas节省很多打字,这是一个非常有用的编程工具。
#include <iostream>
#include <map>
class BaseClass {
public:
virtual void Print() = 0;
};
class ClassA : public BaseClass {
public:
void Print() { std::cout << "Hello "; }
};
class ClassB : public BaseClass {
public:
void Print() { std::cout << "World!n"; }
};
typedef BaseClass * (*factoryMethod) (void);
std::map <std::string, factoryMethod> factories = {
{ "a", []()->BaseClass * {return new ClassA(); } },
{ "b", []()->BaseClass * {return new ClassB(); } }
};
int main (void)
{
BaseClass * foo = factories["a"]();
BaseClass * bar = factories["b"]();
foo->Print();
bar->Print();
return 0;
}
这让你可以做一些巧妙的技巧,比如可以使用枚举以外的东西,因为你不再绑定到switch
。此外,它还允许您使用相同的代码处理不同的逻辑关联(在解析器中出现)。
PS我在上面的代码中没有使用枚举,但想法是一样的。根据我的经验,每当我遇到这个问题时,就更容易将字符串与预期行为直接关联起来,所以我坚持这样做。
相关文章:
- 枚举位域和聚合初始化
- 使用 int 初始化枚举类
- 大括号使用枚举类初始化静态常量unordered_map
- 带有constexpr std :: string_view vs用std :: string实例化枚举的枚举
- 初始化枚举类 (C++11) 类型的二维 std::数组
- 如何部分专用化枚举值的类模板?
- 枚举可以在成员初始化列表中初始化吗?
- 为什么不用static_cast的整数来初始化强大的枚举
- 使用 C++11 可变参数模板初始化枚举到字符串映射
- (取消)序列化枚举类
- 枚举类默认初始化
- '='应初始化所有枚举成员或仅初始化第一个枚举成员;
- 通过显式转换函数初始化枚举类类型的静态constexpr类成员
- 枚举和初始化类
- 枚举初始化混乱
- 由枚举初始化的向量
- 值初始化枚举的行为
- 使用函数调用初始化枚举值
- 初始化枚举c++
- 编译器正在尝试从 int 初始化枚举类,而它没有