Динамическое создание экземпляра класса из строки, содержащей имя класса в С++
Допустим, у меня есть базовый класс со 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
...
};
Во время выполнения я хочу прочитать файл, который содержит дочерние элементы для создания и подачи. Допустим, что я прочитал файл, а вектор строк "имена" содержит имена дочерних классов (например, 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", она возвращает "новый Child1", если строковый аргумент "Child4", он возвращает "новый 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>
}
Ответы
Ответ 1
С++ не предоставляет метод динамического построения экземпляров класса, подобных этому. Однако вы можете использовать генерацию кода для генерации кода "грубой силы" (как показано выше) из списка классов. Затем #include
сгенерированный код в вашем методе convert_string_to_instance
.
Вы также можете настроить свою систему построения проектов для восстановления сгенерированного кода в любое время, когда список классов изменится.
Ответ 2
Я задал вопрос, который называется автоматическая регистрация функции создателя объекта с макросом, которая имеет следующую примерную программу, которая запускается:
#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
Ответ 3
Если у вас много классов, вы обычно выбираете менее грубую силу. Хорошим подходом является trie или hash_map между именами классов и factory.
Вы можете использовать подход cegegen, предложенный Грегом для создания этой таблицы factory, например, doxygen может анализировать ваш исходный код и выводить список всех классов в формате xml вместе с отношениями наследования, поэтому вы можете легко найти все классы, основанные на общем базовом классе "интерфейс".
Ответ 4
Вы можете злоупотреблять препроцессором и настраивать некоторые статические члены класса, которые регистрируют ваши классы с помощью factory через hash_map, как это описывает Бен. Если у вас есть визуальная студия, посмотрите, как DECLARE_DYNCREATE реализована в MFC. Я сделал что-то подобное для реализации класса factory. Нестандартно, но поскольку С++ не предлагает какой-либо поддержки для этого типа механизмов, любое решение, вероятно, будет нестандартным.
Изменить
Я сказал в комментарии ранее, что я работал над документированием уменьшенной версии того, что я сделал. Уменьшенная версия по-прежнему довольно большая, поэтому Я разместил ее здесь. Если есть достаточный интерес, я могу скопировать/вставить его на этот сайт. Дайте мне знать.
Ответ 5
Похоже, вы можете использовать подклассы для вещей, которые должны быть закодированы как поля.
Вместо того, чтобы кодировать различное поведение в 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();
}
Эта идея может быть неприменима к вашей проблеме, но я полагаю, что это не помешает представить ее в любом случае.: -)
Моя идея похожа на рефакторинг Replace Subclass with Fields, но я иду немного дальше.
Ответ 6
Это скелет ужасного, ужасного способа сделать это:
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
. Очевидные недостатки с приведенным выше кодом, как написано, включают огромный потенциал утечек памяти и общую ужасность сочетания макросов и шаблонов.
Ответ 7
Вот мощное усилие.
Единственное, что вам нужно сделать, чтобы использовать мое решение, - это добавить новый член ко всем вашим классам, а это 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 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;
}