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

It's avoidable using the factory pattern, but you still need a bunch of boilerplate code to get off the ground. For example:

// 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.

Then, when you want to instantiate an object, you can do this:

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

Whether this is better than just doing a series of if/else... string comparisons is not clear, since it depends on what exactly you're doing to be doing with this.

于 2013-07-26T05:06:19.043 に答える
1

sを使用してAdam Rosenfield のソリューションを2 番目に使用し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

あなたが言語で何をしているのかについてのサポートはありません。それでも、次のパターンを使用して設計を簡素化できます。

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 に答える
0

C++ は「クラス ベース」の言語です。つまり、クラスの構造はコンパイル時にしかわかりません。したがって、実行時に型を生成することはできません。

実行時にクラス名しかわからない場合を除き、そのようなクラスのインスタンス化は避けたほうがよいでしょう。

これを大規模に行う必要がある場合は、jinja などのサードパーティのコード ジェネレーターを検討してください。テンプレートからファクトリを作成し、特定のマッピング「文字列」->「クラス名」を作成するのに役立ちます。

于 2013-07-26T05:40:46.893 に答える
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

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 に答える