Динамическое создание экземпляра класса из строки, содержащей имя класса в C ++ - PullRequest
11 голосов
/ 04 июня 2011

Допустим, у меня есть базовый класс на 100 детей:

class Base { 
  virtual void feed();
  ...   
};
class Child1 : public Base {
  void feed();  //specific procedure for feeding Child1
  ... 
};
...
class Child100 : public Base { 
  void feed();  //specific procedure for feeding Child100
  ...
};

Во время выполнения я хочу прочитать файл, содержащий информацию о том, какие дочерние элементы создавать и кормить. Допустим, я прочитал файл, а вектор строк «names» содержит имена дочерних классов (т. Е. Child1, Child4, Child99). Теперь я собираюсь перебрать эти строки, создать экземпляр определенного дочернего элемента и передать его с помощью специальной процедуры подачи:

vector<Base *> children;    
for (vector<string>::iterator it = names.begin(); it != names.end(); ++it) {
  Base * child = convert_string_to_instance(*it)       
  child->feed()
  children.push_back(child);
}

Как бы я создал функцию convert_string_to_instance () так, чтобы, если она принимает строку «Child1», она возвращала «new Child1», если аргумент строки «Child4», она возвращает «new Child4» и т. Д. *

<class C *> convert_string_to_instance(string inName) {
  // magic happens
  return new C;  // C = inName

  // <brute force?>
  // if (inName == "Child1")
  //   return new Child1;
  // if (inName == "Child2")
  //   return new Child2;    
  // if (inName == "Child3")
  //   return new Child3;    
  // </brute force>
  }

Ответы [ 7 ]

4 голосов
/ 04 июня 2011

C ++ не предоставляет метод для динамического конструирования экземпляров классов, подобных этому.Однако вы можете использовать генерацию кода для генерации кода "грубой силы" (как вы показали выше) из списка классов.Затем #include сгенерированный код в вашем методе convert_string_to_instance.

Вы также можете настроить систему сборки своего проекта на перестройку сгенерированного кода в любое время, когда список классов изменяется.

3 голосов
/ 04 июня 2011

Я задал вопрос, озаглавленный Автоматическая регистрация функции создателя объекта с помощью макроса , который имеет следующий пример программы, которая запускается:

#include <map>
#include <string>
#include <iostream>

struct Object{ virtual ~Object() {} }; // base type for all objects

struct ObjectFactory {
  static Object* create(const std::string& id) { // creates an object from a string
    const Creators_t::const_iterator iter = static_creators().find(id);
    return iter == static_creators().end() ? 0 : (*iter->second)(); // if found, execute the creator function pointer
  }

 private:
  typedef Object* Creator_t(); // function pointer to create Object
  typedef std::map<std::string, Creator_t*> Creators_t; // map from id to creator
  static Creators_t& static_creators() { static Creators_t s_creators; return s_creators; } // static instance of map
  template<class T = int> struct Register {
    static Object* create() { return new T(); };
    static Creator_t* init_creator(const std::string& id) { return static_creators()[id] = create; }
    static Creator_t* creator;
  };
};

#define REGISTER_TYPE(T, STR) template<> ObjectFactory::Creator_t* ObjectFactory::Register<T>::creator = ObjectFactory::Register<T>::init_creator(STR)

namespace A { struct DerivedA : public Object { DerivedA() { std::cout << "A::DerivedA constructor\n"; } }; }
REGISTER_TYPE(A::DerivedA, "A");

namespace B { struct DerivedB : public Object { DerivedB() { std::cout << "B::DerivedB constructor\n"; } }; }
REGISTER_TYPE(B::DerivedB, "Bee");

namespace C { struct DerivedC : public Object { DerivedC() { std::cout << "C::DerivedC constructor\n"; } }; }
REGISTER_TYPE(C::DerivedC, "sea");

namespace D { struct DerivedD : public Object { DerivedD() { std::cout << "D::DerivedD constructor\n"; } }; }
REGISTER_TYPE(D::DerivedD, "DEE");

int main(void)
{
  delete ObjectFactory::create("A");
  delete ObjectFactory::create("Bee");
  delete ObjectFactory::create("sea");
  delete ObjectFactory::create("DEE");
  return 0;
}

скомпилируйте и запустите вывод:

> g++ example2.cpp && ./a.out
A::DerivedA constructor
B::DerivedB constructor
C::DerivedC constructor
D::DerivedD constructor
2 голосов
/ 04 июня 2011

Если у вас много классов, вы обычно выбираете менее грубый подход.Хорошим подходом является использование trie или hash_map между именами классов и фабричными функциями.

Вы можете использовать подход codegen, предложенный Грегом, для построения этой фабричной таблицы, например, doxygen может проанализировать ваш исходный код и вывести списоквсе классы в формате xml вместе со связями наследования, чтобы вы могли легко найти все классы, производные от общего базового класса «interface».

1 голос
/ 04 июня 2011

Похоже, вы используете подклассы для вещей, которые должны быть закодированы как поля.

Вместо того, чтобы кодировать другое поведение в 100 классах, рассмотрите возможность создания справочной таблицы с указателями правил / констант / функций, которые позволяют вам реализовать правильное поведение из одного класса.

Например, вместо:

class SmallRedSquare  : public Shape {...};
class SmallBlueSquare : public Shape {...};
class SmallBlueCircle : public Shape {...};
class SmallRedCircle  : public Shape {...};
class BigRedSquare    : public Shape {...};
class BigBlueSquare   : public Shape {...};
class BigBlueCircle   : public Shape {...};
class BigRedCircle    : public Shape {...};

попробовать:

struct ShapeInfo
{
   std::string type;
   Size size;
   Color color;
   Form form;
};

class Shape
{
public:
    Shape(std::string type) : info_(lookupInfoTable(type)) {}

    void draw()
    {
        // Use info_ to draw shape properly.
    }

private:
    ShapeInfo* lookupInfoTable(std::string type) {info_ = ...;}

    ShapeInfo* info_;
    static ShapeInfo infoTable_[];
};

const ShapeInfo Shape::infoTable_[] =
{
    {"SmallRedSquare",  small,  red, &drawSquare},
    {"SmallBlueSquare", small, blue, &drawSquare},
    {"SmallRedCircle",  small,  red, &drawCircle},
    {"SmallBlueCircle", small, blue, &drawCircle},
    {"BigRedSquare",      big,  red, &drawSquare},
    {"BigBlueSquare",     big, blue, &drawSquare},
    {"BigBlueCircle",     big,  red, &drawCircle},
    {"BigRedCircle",      big, blue, &drawCircle}
}

int main()
{
    Shape s1("SmallRedCircle");
    Shape s2("BigBlueSquare");
    s1.draw();
    s2.draw();
}

Эта идея может быть неприменима к вашей проблеме, но я полагаю, что в любом случае представить ее не помешает. : -)

Моя идея похожа на Заменить подкласс на поля рефакторинг, но я пойду немного дальше.

1 голос
/ 04 июня 2011

Вы можете злоупотребить препроцессором и настроить некоторые статические члены класса, которые регистрируют ваши классы на фабрике через hash_map, как описывает Бен. Если у вас есть Visual Studio, посмотрите, как DECLARE_DYNCREATE реализовано в MFC. Я сделал нечто подобное для реализации фабрики классов. Конечно, нестандартно, но, поскольку C ++ не предоставляет никакой поддержки для этого типа механизма, любое решение, вероятно, будет нестандартным.

Редактировать

Я сказал в комментарии ранее, что работал над документированием уменьшенной версии того, что я сделал. Уменьшенная версия все еще довольно большая, поэтому я разместил ее здесь . Если есть интерес, я могу скопировать / вставить его на этом сайте. Дайте мне знать.

0 голосов
/ 04 июня 2011

Вот могучий Усилитель.

Единственное, что вам нужно сделать, чтобы использовать мое решение, это добавить нового члена ко всем вашим классам, и это static const string, который содержит имякласс.Возможно, есть и другие способы сделать это, но это то, что у меня есть сейчас.

#include <iostream>
#include <vector>
#include <string>

#include <boost/fusion/container/list/cons.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/view/iterator_range.hpp>

using namespace std;
using boost::fusion::cons;


class Base { virtual void feed(){ } };

class Child1 : public Base{
  void feed(){ }

public:
  static const string name_;
};
const string Child1::name_ = "Child1";

class Child3 : public Base{
  void feed(){ }

public:
  static const string name_;
};
const string Child3::name_ = "Child3";

//...
class Child100 : public Base{

  void feed(){ }

public:
  static const string name_;
};
const string Child100::name_ = "Child100";

// This is probably the ugliest part, but I think it's worth it.
typedef cons<Child1, cons<Child3, cons<Child100> > > MyChildClasses;

typedef vector<Base*> Children;
typedef vector<string> Names;

struct CreateObjects{      // a.k.a convert_string_to_instance() in your example.

  CreateObjects(Children& children, string name) : children_(&children), name_(name){ }

  template <class T>
  void operator()(T& cs) const{

    if( name_ == cs.name_ ){
      cout << "Created " << name_ << " object." << endl;
      (*children_).push_back(new T);
    }else{
      cout << name_ << " does NOT match " << cs.name_ << endl;
    }
  }

  Children* children_;
  string name_;
};

int main(int argc, char* argv[]){

  MyChildClasses myClasses;

  Children children;
  Names names;
  names.push_back("Child1");
  names.push_back("Child100");
  names.push_back("Child1");
  names.push_back("Child100");

  // Extra test.
  // string input;
  // cout << "Enter a name of a child class" << endl;
  // cin >> input;
  // names.push_back(input);

  using namespace boost::fusion;
  using boost::fusion::begin;
  using boost::fusion::for_each;

  for(Names::iterator namesIt = names.begin(); namesIt != names.end(); ++namesIt){

    // You have to know how many types there are in the cons at compile time.
    // In this case I have 3; Child1, Child3, and Child100
    boost::fusion::iterator_range<
      result_of::advance_c<result_of::begin<MyChildClasses>::type, 0>::type,
      result_of::advance_c<result_of::begin<MyChildClasses>::type, 3>::type
      > it(advance_c<0 >(begin(myClasses)),
       advance_c<3>(begin(myClasses)));
    for_each(it, CreateObjects(children, *namesIt));
  }

  cout << children.size() << " objects created." << endl;
  return 0;
}
0 голосов
/ 04 июня 2011

Это скелет ужасного, ужасного способа сделать это:

class Factory {
  public:
    virtual Base * make() = 0;
};

template<typename T> class TemplateFactory : public Factory {
  public:
    virtual Base * make() {
      return dynamic_cast<Base *>(new T());
    }
};

map<string, Factory *> factories;

#define REGISTER(classname) factories[ #classname ] = new TemplateFactory<classname>()

Затем вызовите REGISTER(classname); для каждого соответствующего производного класса Base и используйте factories["classname"]->make(), чтобы получить новыйобъект типа classname.Очевидные недостатки написанного выше кода включают огромный потенциал для утечек памяти и общую ужасную комбинацию макросов и шаблонов.

...