13

Lets say I have a base class with 100 children:

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
  ...
};

At runtime I want to read a file that contains which children to create and feed. Lets say I've read the file and the vector of strings "names" contains the names of the child classes (ie. Child1, Child4, Child99). Now I'm going to iterate through these strings, create an instance of the specific child, and feed it with its specific feeding procedure:

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);
}

How would I create the function convert_string_to_instance() such that if it takes in the string "Child1" it returns a "new Child1", if the string argument is "Child4" it returns a "new Child4", etc

<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>
  }
Scott
  • 131
  • 1
  • 3
  • Dynamic classes in C++ that smells like refraction. I don't know how to do that without the "brute force" attempt. I'd love to know how. – Vinnyq12 Jun 04 '11 at 02:29
  • basically this: http://stackoverflow.com/questions/41453/how-can-i-add-reflection-to-a-c-application there are some systems out there for some advanced reflection like this: http://root.cern.ch/drupal/content/reflex but they all require an extra build step to extract the metadata – PeterT Jun 04 '11 at 02:34
  • 1
    This would be about the most superbly constructed question I've seen on StackOverflow for a while. Just the right amount of detail and I love the section. Sadly, I think that variations on that theme are the only answer. – Bill Forster Jun 04 '11 at 02:36

7 Answers7

4

C++ does not provide a method for dynamic construction of class instances like this. However, you may be able to use code generation to generate the "brute force" code (like you showed above) from a list of classes. Then, #include the generated code in your convert_string_to_instance method.

You can also set up your project build system to rebuild the generated code anytime the list of classes changes.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
4

I asked a question entitled automatic registration of object creator function with a macro that has the following example program that runs:

#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;
}

compile and run output is:

> g++ example2.cpp && ./a.out
A::DerivedA constructor
B::DerivedB constructor
C::DerivedC constructor
D::DerivedD constructor
Community
  • 1
  • 1
McKay.CPP
  • 440
  • 1
  • 3
  • 11
2

If you have a lot of classes, you'd usually choose a less brute force approach. A trie or hash_map between class names and factory functions is a good approach.

You can use a codegen approach as suggested by Greg to build this factory table, for example doxygen can parse your source code and output a list of all classes in xml format along with inheritance relationships, so you could easily find all classes deriving from a common "interface" base class.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Reason for downvote? For that matter, I'd like to know the reason most of the answers got downvoted. Looks like all except @Red's, oddly enough. – Ben Voigt Jun 04 '11 at 06:24
  • Yeah that is interesting isn't it? I'm working on a scaled down version of what I did either to post here if this thread doesn't get too old or on my blog. It's much along the lines of @McKay and @Peter's examples. I'm giving +1's because these are all useful answers. – Dave Rager Jun 04 '11 at 13:21
2

You can abuse the preprocessor and set up some static class members that register your classes with a factory via a hash_map like Ben describes. If you have visual studio, look at how DECLARE_DYNCREATE is implemented in MFC. I've done something similar to implement a class factory. Non-standard for sure but since C++ does not offer any kind of support for this type of mechanism any solution is probably going be non-standard.

Edit

I said in a comment earlier I was working on documenting a scaled down version of something I had done. The scaled down version is still rather large so I posted it here. If there is enough interest I can copy/paste it on this site. Let me know.

Dave Rager
  • 8,002
  • 3
  • 33
  • 52
2

It sounds like you might be using subclasses for things that should be encoded as fields.

Instead of coding the different behaviour in 100 classes, consider building a look-up table with rules/constants/function-pointers that allow you to implement the proper behaviour from one class.

For example, instead of:

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 {...};

try:

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();
}

This idea might not be applicable to your problem, but I figure it couldn't hurt to present it anyway. :-)

My idea is like the Replace Subclass with Fields refactoring, but I go a bit further.

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
1

This is the skeleton of a horrible, horrible way to do it:

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>()

Then call REGISTER(classname); for every relevant derived class of Base, and use factories["classname"]->make() to get a new object of type classname. Obvious flaws with the above code as written include massive potential for memory leaks, and the general awfulness of combining macros and templates.

Peter Milley
  • 2,768
  • 19
  • 18
1

Behold the mighty Boost.

The one thing you have to do in order to use my solution is to add a new member to all your classes, and that is a static const string that contains the name of the class. There are probably other ways to do it too, but that's what I have right now.

#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;
}
Arlen
  • 6,641
  • 4
  • 29
  • 61