views:

78

answers:

4

Suppose I have a class Image. At some point in parsing, "Image" is read in at the appropriate time, meaning I want to create an object of class Image.

What I'm considering is mapping these strings to a constructor call to the appropriate class, but I'm not sure how to accomplish this.

I.e.

container.push_back( some_map[stringParsedIn] ); // basic idea
A: 

You're describing a factory function. There are many ways to accomplish this, from registries to simple if/else chains.

Usually your Image class derives from a similar base class to other "parsed in" types. That way, you can add them all to the same container.

Imagine this hierarchy:

class Media {
 public:
  virtual Save() = 0;
};

class Image : public Media {
 public:
   Image() { }
   virtual Save() { ... }
};

class Sound : public Media {
 public:
   Sound() { }
   virtual Save() { ... }
};

The simplest construct is a factory function:

Media *CreateMedia(const string &type) {
  if (type == "Image") {
    return new Image;
  } else if (type == "Sound") {
    return new Sound;
  } else {
    // handle invalid type error
  }
}

Another alternative is to use a registry, instead of CreateMedia you would usually use a macro, a factory/registry and some mechanism to create your subclasses:

// This is some mechanism to create types of Media.
template <typename T>
struct CreatorFunction {
  Media *operator() {
    return new T;
  }
};

// This is the factory that the types will register with.
class Factory {
 public:
  // singleton access function.
  static Factory* Get() {
    static Factory* f = new Factory; 
    return f;
  }

  // Creates Media of the given type.
  Media* Create(const string& name) { return registry_[name](); }

  // Records 'name' with the creator function 'func'.
  void Add(const string& name, const CreatorFunction &func) {
    registry_.insert(name, func);
  }
 private:
  Factory() { } // users can't create factories, they can only use singleton.
  hash_map<string, CreatorFunction> registry_;
};

#define REGISTER_MEDIA(type) Factory::Get()->Add(#Image, CreatorFunction<type>);

REGISTER_MEDIA(Image);  // usually goes with the Image class.
REGISTER_MEDIA(Sound);  // usually goes with the Sound class.

int main(int argc, char** argv) {
  string parsedIn = "Image";
  Factory::Get()->Create(parsedIn);
}

This is a cleaner approach overall, but you may have problems with your linker thinking some symbols are unused and trimming the important registered classes out of your binary. You'll probably want to stick with the if/then chaining until you need something more sophisticated. You usually go for registries once it's infeasible to have a single location where all subtypes are defined.

Stephen
A: 

You can't store function pointers to constructors, but you could store a pointer to a function which returns a newly constructed object, i.e.

Image *createImage() {
    return new Image();
}

You could then store a pointer to this function in your map.

std::map<std::string, Image *(*)()> constructorMap;
constructorMap.insert(std::pair<std::string, Image *(*)()>("Image", createImage));

And then call it with

Image *myImage = constructorMap["Image"]();
robinjam
A: 

I am not 100% sure what you are asking, but I'll give it a guess.

You could wrap the constructors in functions:

Image* makeImage(ArgType arg) { return new Image(arg); }

And then you can store function pointers in your map!

map["Image"] = makeImage;

Later to call them!

SuperclassOfImage soup = map["Image"](arg);

Of course the limitation here is that the type signatures of the functions must take the same type argument and must return the same type (an instance of a class that is either Image or a parent of Image).

kerkeslager
A: 

As Stephen pointed out, what you are describing is the Factory pattern (assuming that Image is an abstract base class). However, for its implementation, it might be helpful to associate strings with creation functions as you described instead of a large function consisting of if/else statements. Here's one way to do it (assuming your image subclasses can all be constructed the same way):

typedef Image* create_image_function();

template <class T>
Image* create_image(SomeType arg)
{
    return new T(arg);
}

...
map<string, create_image_function> creators;
creators["Foo"] = &create_image<Foo>;
creators["Bar"] = &create_image<Bar>;
creators["Baz"] = &create_image<Baz>;

shared_ptr<Image> ImageFactory::make_image(const string& str)
{
    // checking to see if str exists as a key 
    // would be nice
    return shared_ptr<Image>(creators[str](arg) );
}