枚举和初始化 类

Enums and initialising classes

我经常遇到这个烦人的怪癖。

说我有类似的东西

enum class Thing {Ex1, Ex2, Ex3}

我有一个 Thing 的实例,想根据它是什么创建一个特定的 class,它最终看起来像这样

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

如果项目数量变大,在 "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,我可以使用 lambda 节省大量输入,以至于这是一个非常有用的编程工具。

#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 上面的代码我没有使用枚举,但是思路是一样的。根据我的经验,每当我遇到这个问题时,将字符串直接与预期行为相关联会更容易,所以我坚持这样做。

C++ 中隐藏了一个代码生成器,名为 Template Metaprogramming。您可以使用它来生成您想要的工厂。您也可以使用 Variadic Templates(即 C++11 功能)。

我来到了下面的代码(我有点懒得实现正确的 "last" 专门化 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    [=11=]x1,%eax
<+31>:    je     0x400a83 <main(int, char**)+275>
<+37>:    cmp    [=11=]x2,%eax
<+40>:    jne    0x400a77 <main(int, char**)+263>

所以它变成了简单的 if-else。不确定是否会产生跳转table.

这里是测试代码:

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,但这并不容易。