6

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

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++ 中有没有更好的方法来处理这种情况?

4

6 回答 6

6

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

// 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...字符串比较更好尚不清楚,因为这取决于您到底在做什么。

于 2013-07-26T05:06:19.043 回答
1

我使用s支持 Adam Rosenfield 的解决方案map。但是,获得更高级别功能的较低级别接口是使用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();

当然,如果该示例实际上是动态获取其名称的(例如从配置文件或从用户输入),则该示例会更有趣。

于 2013-07-26T07:17:00.477 回答
0

没有办法像在 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 对象,那么您可以使用一些技巧来使用单例工厂自动注册您的类,但我认为这是一个这样做的更好主意并避免单例。

于 2013-07-26T05:36:25.983 回答
0

主要区别在于,与 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函数内部。

于 2013-07-26T05:27:43.507 回答
0

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

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

如果需要大规模执行此操作,请查看第三方代码生成器,例如 jinja。它将帮助您根据模板和给定的映射“字符串”->“类名”创建工厂。

于 2013-07-26T05:40:46.893 回答
0

不支持您在该语言中所唱的内容。不过,您可以使用以下模式来简化您的设计:

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在基类中可以创建任何在列表中注册自身的派生类。

于 2013-07-26T05:23:46.957 回答