views:

233

answers:

3

Suppose I have a list of classes A, B, C, ... which all inherit from Base.

I get the class name as a string from the user, and I want to instantiate the right class and return a pointer to Base. How would you implement this?

I thought of using a hash-table with the class name as the key, and a function pointer to a function that instantiates the right class and returns a Base *.

However, I think I might be able to use the factory pattern here and make it a lot easier, but I just can't quite remember it well, so I though I'd ask for suggestions.

+2  A: 

the quickest yet very usable way in a lot of areas, would be something like

Base* MyFactoryMethod( const std::string& sClass ) const
{
  if( sClass == "A" )
    return CreateNewA();
  else if( sClass == "B" )
    return new CreateClassB();
  //....
  return 0;
}

A* CreateClassA() const
{
  return new A();
}
stijn
Yeah, I also usually start with something similar - class with similar "create" method. Then if there a lot of classes(or library user is supposed to add some) I add the "true" factory behind it (map,hash,whatever) with registrations.
MaR
maybe i should read about the factory pattern... i can't remember how it is implemented.
Idan
+2  A: 

First off, yes, that is just what the factory pattern is for.
(By the way, your other idea is a possible implementation of the factory pattern)

If you intend to do this for a large project (if not, just go with stijns answer), you might want to consider using an associative container somewhere instead of explicit branching and maybe even moving the registration responsibility into the classes to

  • avoid code changes in one additional place (your factory)
  • and in turn avoid possibly very long recompilation times (for in-header-implementations) when adding a class

To achieve convenient registration in the classes you could use something like this suggestion and add a function pointer or a functor to the entries that instantiates the derived class and returns a pointer to the base.
If you're not afraid of macros you can then add classes to the factory by just adding one tiny macro to its declaration.

Georg Fritzsche
+4  A: 

Here is a generic factory example implementation:

template<class Interface, class KeyT=std::string>
struct Factory {
    typedef KeyT Key;
    typedef std::auto_ptr<Interface> Type;
    typedef Type (*Creator)();

    bool define(Key const& key, Creator v) {
        // Define key -> v relationship, return whether this is a new key.
        return _registry.insert(typename Registry::value_type(key, v)).second;
    }
    Type create(Key const& key) {
        typename Registry::const_iterator i = _registry.find(key);
        if (i == _registry.end()) {
            throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
                                        ": key not registered");
        }
        else return i->second();
    }

    template<class Base, class Actual>
    static
    std::auto_ptr<Base> create_func() {
        return std::auto_ptr<Base>(new Actual());
    }

private:
    typedef std::map<Key, Creator> Registry;
    Registry _registry;
};

This is not meant to be the best in every circumstance, but it is intended to be a first approximation and a more useful default than manually implementing the type of function stijn mentioned. How each hierarchy should register itself isn't mandated by Factory, but you may like the method gf mentioned (it's simple, clear, and very useful, and yes, this overcomes the inherent problems with macros in this case).

Here's a simple example of the factory:

struct Base {
    typedef ::Factory<Base> Factory;
    virtual ~Base() {}
    virtual int answer() const = 0;

    static Factory::Type create(Factory::Key const& name) {
        return _factory.create(name);
    }
    template<class Derived>
    static void define(Factory::Key const& name) {
        bool new_key = _factory.define(name,
            &Factory::template create_func<Base, Derived>);
        if (not new_key) {
            throw std::logic_error(std::string(__PRETTY_FUNCTION__) +
                                   ": name already registered");
        }
    }

private:
    static Factory _factory;
};
Base::Factory Base::_factory;

struct A : Base {
    virtual int answer() const { return 42; }
};

int main() {
    Base::define<A>("A");
    assert(Base::create("A")->answer() == 42);
    return 0;
}
Roger Pate