是C++中由于缺少反射而不可避免的一个大开关块

Is a big switch block unavoidable in C++ due to lack of reflection

本文关键字:一个 开关 C++ 于缺少 反射 不可避免      更新时间:2023-10-16

假设我有一个类的层次结构:

class Shape {
};
class Circle : public Shape {
}
class Square : public Shape {
}
... hundreds of other shapes continue on...

当将形状类的名称作为字符串时,我需要实例化该类的对象。

在java中,我可以做这样的事情(伪代码!)

Shape createShape(String name) {
    return new Class.forName(name);
}

但在C++中,我必须这样做:(伪代码!)

Shape * createShape(const string &name) {
    if (name.compare("Circle") == 0) {  
        return new Circle();
    }
    else if (name.compare("Square") == 0) {
        return new Square();
    }
    else if ... //hundreds of else if continues, one for each shape
}

在C++中有更好的方法来处理这种情况吗?

使用工厂模式是可以避免的,但您仍然需要一堆样板代码才能开始工作。例如:

// Class factory functions -- these could also be inlined into their respective
// class definitions using a macro
Shape *createCircle() { return new Circle(); }
Shape *createSquare() { return new Square(); }
// etc.
// Create a map from type name to factory
typedef std::map<std::string, Shape *(*)()> ShapeFactoryMap;
ShapeFactoryMap factoryMap;
factoryMap["Circle"] = &createCircle;
factoryMap["Square"] = &createSquare;
// etc.

然后,当你想实例化一个对象时,你可以这样做:

ShapeFactoryMap::iterator factory = factoryMap.find("Circle");
if (factory != factoryMap.end())
{
    Shape *circle = factory->second();  // Creates a Circle instance
    ...
}
else
{
    // Handle error
}

这是否比只进行一系列if/else...字符串比较更好尚不清楚,因为这取决于你到底在做什么。

我支持Adam Rosenfield使用map s的解决方案。然而,获得更高级别功能的较低级别接口是使用dlsym()查找。

假设您的通用Shape接口位于文件Shape.hpp中,并且具有以下形式:

class Shape {
public:
    virtual ~Shape () {}
    //...virtual methods
    virtual void draw () const = 0;
};
template <typename DERIVED>
class ShapeBridge : public Shape {
public:
    static Shape * create () { return new DERIVED; }
};
struct ShapeFactory {
    Shape * (*create) ();
};

假设您想通过创建一个新的共享对象来动态添加一个新形状,然后将其动态链接到现有的运行中的可执行文件中。然后,您现在可以创建一个抽象的工厂,它使用共享对象的动态加载来获得具体的工厂函数:

#include <string>
#include <map>
#include <dlfcn.h>
struct ShapeCreator {
    void *dlhandle_;
    void *factory_;
    ShapeCreator () : dlhandle_(0), factory_(0) {}
    void open (std::string libname) {
        dlhandle_ = dlopen(libname.c_str(), RTLD_LAZY);
        factory_ = dlsym(dlhandle_, "factory");
    }
    void close () { if (dlhandle_) dlclose(dlhandle_); }
    ShapeFactory * factory () const {
        return static_cast<ShapeFactory *>(factory_);
    }
    static Shape * create (std::string name) {
        static std::map<std::string, ShapeCreator> lookup;
        static std::string dir = "./";
        if (lookup[name].factory() == 0) {
            lookup[name].open(dir + name + ".so");
    }
        return lookup[name].factory()->create();
    }
};

您的共享对象可能有以下实现:

// gcc -fPIC  -shared -Wl,-export-dynamic -o Circle.so Circle.cpp -lc
#include "Shape.hpp"
#include <iostream>
class Circle : public ShapeBridge<Circle> {
public:
    //..
    void draw () const { std::cout << "I am a circle.n"; }
};
extern "C" {
    ShapeFactory factory = { Circle::create };
}

然后动态创建形状:

    Shape *s = ShapeCreator::create("Circle");
    s->draw();

当然,如果这个例子实际上是动态地(比如从配置文件或用户输入)获得它的名称,那么它会更有趣一些。

主要区别在于,与Java不同,C++没有像forName(String)这样的内置函数,它为您完成任务。在C++中,你必须实现它。

现在重要的是你如何做这些事情。switch/case提出的方法是一种方法,它是一种直接而漫长的方法。你可以自动化:

(1) 首先引入一个中间template class,它创建一个对象,这样就不必为每个类实现方法。

template<class Derived>
class ShapeCreator : public Shape {  // This class automates the creations
public:
  static Shape* Create () {
    new Derived();  // Assuming that no-argument default constructor is avaialable
  }
};
class Circle : public ShapeCreator<Circle> {
};
class Square : public ShapeCreator<Square> {
};
//... and so on

(2) 现在在class Shape中,引入一个static std::map,它为每个派生类保留一个句柄。

class Shape {
public:
  typedef std::map<std::sting, Shape* (*)()> ShapeMap;
  static ShapeMap s_ShapeMap;
  static Shape* Create (const std::string name) {
    ShapeMap::iterator it = s_ShapeMap.find(name);
    if(it == s_ShapeMap.end())
      return 0;
    it->second();
  }
};

(3) 填充s_ShapeMap必须以静态方式完成,您可以选择在调用main()之前执行(执行此操作时要小心),也可以将其作为main()中的第一个函数。使用预处理器技巧来自动化事情:

#define INIT(SHAPE) Shape::s_ShapeMap[#SHAPE] = &SHAPE::Create
Shape* InitializeShapeMap () {
  INIT(Circle);
  INIT(Square);
  INIT(Triangle);
  // ...
}
#undef INIT

每当引入任何新形状时,只需将其作为INIT添加到函数中即可。

C++是一种"基于类"的语言,这意味着类的结构只有在编译时才知道。因此,您无法在运行时生成类型。

除非您只在运行时知道类名,否则最好避免这种类实例化。

如果需要大规模执行,请查看第三方代码生成器,如jinja。它将帮助您根据模板和给定的映射"string"->"类名"创建一个工厂。

没有办法像Java中那样随心所欲,但有一些方法可以让它比一个巨大的switch语句稍微不那么痛苦。你需要某种工厂。就我个人而言,我喜欢使用以下内容:

class ShapeBase
{
};
template<class TShape>
class Shape: public ShapeBase
{
public:
    typedef TShape shape_type;

    template< class TFactory >
    static void registerClass(TFactory* factory)
    {
        factory->registerShape(shape_type::name(), [](){ return new shape_type(); });
    }
};

class Circle: public Shape<Circle>
{
public:
    static const char* name() { return "Circle"; }
};
class Square: public Shape<Square>
{
public:
    static const char* name()  { return "Square"; }
};
class ShapeFactory
{
private:
    typedef std::function<ShapeBase*()> shape_creator;
    std::map<std::string,shape_creator> _creators;
public:
    ShapeFactory()
    {
        registerShapes();
    }
    void registerShapes()
    {
        Square::registerClass(this);
        Circle::registerClass(this);
    }

    void registerShape( const std::string& name, shape_creator creator )
    {
        _creators[name] = creator;
    }
    ShapeBase* create(const std::string& name)
    {
        return _creators[name]();
    }
};
int main( int argc, char** argv )
{
    ShapeFactory factory;
    ShapeBase* circle = factory.create("Circle");
    ShapeBase* square = factory.create("Square");
    return 0;
}

如果你可以在可执行组件或动态库中定义所有的Shape对象,而不是在静态库中,那么你可以使用一些技巧来向singleton工厂自动注册你的类,但我认为这样做并避免singleton是更好的主意。

该语言不支持您正在使用的内容。尽管如此,您可以使用以下模式来简化您的设计:

class Shape
{
    Shape *CreateShape(const char *name)
    {
       // Iterate single linked list of known derived classes.
       Node *item = ListOfDerivedClasses;
       while (item != NULL)
       {
           if (strcmp(item->name, name) == 0)
               return item->factory();
           item = item->next; 
       }
    }
    typedef Shape *CreateShapeInstance();
    struct Node
    {
        char *name;
        CreateShapeInstance *factory;
        Node *next;
        Node(char *n, CreateShapeInstance *f)
        {
            name = n; factory = f;
            next = Shape::ListOfDerivedClasses;
            Shape::ListOfDerivedClasses = this;
        }
    };
    static Node *ListOfDerivedClasses;
};
class Circle : public Shape
{
    static Shape *CreateInstance() { return new Circle(); }
}
static Shape::Node circle_info("Circle", Circle::CreateInstance);

其思想是,只包含静态元素的单链表是在静态对象初始化期间创建的,之后再也不会修改。这种设计允许在不修改基类的情况下添加派生类,而基类中的CreateShape可以创建任何在列表中注册的派生类。