假设我有一个有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>
}
答案 0 :(得分:4)
C ++没有提供像这样动态构造类实例的方法。但是,您可以使用代码生成从类列表中生成“强力”代码(如上所示)。然后,#include
convert_string_to_instance
方法生成的代码。{/ p>
您还可以设置项目构建系统,以便在类列表发生更改时重建生成的代码。
答案 1 :(得分:3)
我问了一个题为automatic registration of object creator function with a macro的问题,该问题包含以下运行的示例程序:
#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 :(得分:2)
如果你有很多课程,你通常会选择一种不那么暴力的方法。类名和工厂函数之间的trie或hash_map是一种很好的方法。
您可以使用Greg建议的codegen方法来构建此工厂表,例如doxygen可以解析您的源代码并输出xml格式的所有类的列表以及继承关系,因此您可以轻松找到所有派生类来自一个共同的“接口”基类。
答案 3 :(得分:1)
您可以滥用预处理器并设置一些静态类成员,这些成员通过像Ben描述的hash_map向工厂注册您的类。如果您有visual studio,请查看在MFC中如何实现DECLARE_DYNCREATE。我做过类似实现类工厂的事情。非标准肯定,但由于C ++不对这种机制提供任何形式的支持,任何解决方案都可能是非标准的。
我之前在评论中说过,我正在努力记录我所做过的缩小版本。按比例缩小的版本仍然相当大I posted it here。如果有足够的兴趣,我可以在这个网站上复制/粘贴它。让我知道。
答案 4 :(得分:1)
听起来您可能正在使用子类来处理应该编码为字段的内容。
不要在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重构一样,但我会更进一步。
答案 5 :(得分:0)
这是一种可怕的,可怕的方式的骨架:
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
的新对象。上面编写的代码有明显的缺陷,包括内存泄漏的巨大可能性,以及组合宏和模板的一般可怕性。
答案 6 :(得分:0)
看到强大的助推器。
要使用我的解决方案,您必须做的一件事是向所有类添加一个新成员,这是一个包含该类名称的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;
}