Sign in to follow this  

Loki - Object Factory (Part II)

This topic is 4732 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

After getting Modern C++ Design, I'm playing around with the Loki library and I have a quick question on the Object Factory. For those who haven't used Loki, I'll try and outline here what my problem is (in a simpler format, without using template meta-programming). The problem's as follows: The factory class requires new objects to register with it, to make adding new objects easier. In code, the relevant parts boil down to something like this:
class ShapeFactory
{
public:
 typedef Shape* (*CreateCallback)();
 typedef std::map<int, CreateCallback> CallbackMap;
public:
 bool RegisterShape(int shapeID, CreateCallback createFn);
private:
 CallbackMap callbacks_;
}; 

bool RegisterShape(int shapeID, CreateCallback createFn)
{
 return callbacks_.insert(CallbackMap::value_type(shapeID, createFn)).second;
}


Fine - so a map of ints and callback functions, and a register function for new objects to register themselves with. My problem comes with how new objects register themselves. In the book it's given as the following, with the comment "The registration is usually performed with startup code":
// Anonymous namespace to insulate code
namespace
{
 Shape* CreateLine()
 {
  return new Line;
 }
 const int LINE = 1;
// Assuming shapefactory is a singleton
 const bool registered = TheShapeFactory::Instance().RegisterShape(LINE, CreateLine);
}


So here's the bit I don't get. You've got to call this code at some point, in order to register the classes with the factory. The code that calls this obviously has to be aware of the Line class. One of the stated objectives with this design is to avoid having "...a single source file [having] knowledge about all Shape-derived types...". But - surely this isn't avoided? Doesn't the calling code have to be something like this:
const bool registered = TheShapeFactory::Instance().RegisterShape(LINE, CreateLine);
const bool registered = TheShapeFactory::Instance().RegisterShape(SQUARE, CreateSquare);
const bool registered = TheShapeFactory::Instance().RegisterShape(CIRCLE, CreateCircle);


which needs to know about all the classes in one file? OR: (all excited now - I think I might have cracked it!) Do you have something like this:
// File GlobalFunctions.h
// Doesn't need to know about line class - just declares RegisterLine function
bool RegisterLine();
bool RegisterSquare();
// etc.

// Files Line.h & Line.cpp; in addition to declaration and definition of Line class
#include "shapefactory.h"
bool RegisterLine()
{
// Anonymous namespace to insulate code
 namespace
 {
  Shape* CreateLine()
  {
   return new Line;
  }
  const int LINE = 1;
// Assuming shapefactory is a singleton
  const bool registered = TheShapeFactory::Instance().RegisterShape(LINE, CreateLine);
 }
}

// Similar things in Square.h/cpp etc.

// In init.cpp
#include "globalfunctions.h"

void DoAllInitialisation()
{
 RegisterLine();
 RegisterSquare();
}


Is that it? Have I answered my own question (again!)? If so - sorry for wasting your time (amazing how useful it is to write stuff down like this). Or have I missed something? Thanks, Jim. [Edited by - JimPrice on December 31, 2004 12:13:02 PM]

Share this post


Link to post
Share on other sites
Nope, the original code is all that is needed. Let's disect it for a bit:


// Anonymous namespace to insulate code
namespace
{
Shape* CreateLine()
{
return new Line;
}
const int LINE = 1;
// Assuming shapefactory is a singleton
const bool registered = TheShapeFactory::Instance().RegisterShape(LINE, CreateLine);
}


This goes at namespace level in a source file. Which means CreateLine() is a normal function with external linkage, though it has special name mangling that prevents it from being referenced from other translation units accidently, or having name collisions.

LINE is a global const int with static intialization (and special name mangling), which means that it's value will probably be set in stone by the executable image before the program execution even begins.

registered, however, is a global variable (again with special name mangling) with dynamic initialization. That means that it calls a function to initialize itself sometime after program execution begins. To initialize registered, it needs to call TheShapeFactory::Instance().RegisterShape(LINE, CreateLine), which presumably returns a value convertible to bool. This is done automatically, it doesn't need to be invoked elsewhere, since it's trying to initialize a global variable.

Share this post


Link to post
Share on other sites
I don't like this example much, because it doesn't make sure that int RECTANGLE in some other file doesn't also use the same integer. And, given that the integers are in the anonymous namespace, and not visible from other translation units, I don't even understand what they're good for. The object factory could easily keep a count of registered class factory functions, and assign IDs itself, rather than using this convoluted way of doing nothing.

If LINE was a member of a global enum, it would make more sense, but then you need a single include file that all the classes include to get the enum values, and adding one class means re-compiling all classes and you're back to square 1.

Perhaps the cleverest way is to declare non-anonymous scope int variables for the different classes, and pass them by reference to the registry function; the registry then allocates an ID and assigns to the (by-reference) argument global. The global int is now a handle to the LINE class, and when you create a line, you can use the symbolic value LINE. Sadly, it can't be const, without const_cast<> inside the factory registration function.

You can now keep one include file per class (line, rect, etc) that just forward delcares this one global int, and you have perfect physical separation between your classes.

Share this post


Link to post
Share on other sites
Edit : in reference to SiCrane, just found hplus's - will append thoughts in a mo.

Ahhh, I see. I've never seen a global variable initialised like that before.

Is there any danger of incorrect initialisation order? For example - say creation of ShapeFactory relies upon another singleton being intialised first (which you have accounted for in your initialisation code). Then if this code is used, could the creation of the global 'const bool registered' happen before the other singleton is created (ie the one ShapeFactory is relying upon)?

Jim.

Edit : hplus - I think I see what you're saying - hmm, raises some more problems I hadn't thought of.

Share this post


Link to post
Share on other sites
Quote:
Original post by JimPrice
Is there any danger of incorrect initialisation order? For example - say creation of ShapeFactory relies upon another singleton being intialised first (which you have accounted for in your initialisation code). Then if this code is used, could the creation of the global 'const bool registered' happen before the other singleton is created (ie the one ShapeFactory is relying upon)?

That's why he uses Loki's singleton implementation, which is designed to solve that exact problem. If the singleton hasn't been created yet, the invocation of the Instance() member should create it. Go re-read chapter 6 in the book again.

Share this post


Link to post
Share on other sites
Quote:
Original post by JimPrice
Is there any danger of incorrect initialisation order? For example - say creation of ShapeFactory relies upon another singleton being intialised first (which you have accounted for in your initialisation code). Then if this code is used, could the creation of the global 'const bool registered' happen before the other singleton is created (ie the one ShapeFactory is relying upon)?

Jim.


Yes. You can't rely on order of instantiation of "linker units" or whatever the correct standardese term is. They could be in any order whatsoever.

Share this post


Link to post
Share on other sites
The verbage goes more like: the order of dynamic initialization of translation units is implementation defined. However, static initiailization is guaranteed to be carried out before dynamic initialization is begun for any translation unit, so the effect of the Loki code, while not completely defined, does not risk using an uninitialized object.

Share this post


Link to post
Share on other sites
Thanks for all the original help. Now the next step (which may end up being hypothetical, but still interested).

Let's say we now have more than one layer of factory. For example:

ResourceManager : manages a list of IndividualResourceManager pointers, each of which is responsible for a single resource (mesh, texture, sound, sandwiches)

MeshManager : is a concrete class of IndividualResourceManager, handles all meshes; handles a list of MeshLoader pointers for loading meshes of different formats (ie milkshape ascii, MD2, MD3)

MeshLoadMilkshape : a concrete class of MeshLoader, responsible for loading a milkshape ascii file and returning a new Mesh* object.

So - when a resource needs to be loaded, a filename is sent to the ResourceManager. The resource manager works out what kind of file it is from the extension, and requests a load from the associated ResourceManager. The resource manager further refines what sort of object it wants and calls the associated MeshLoader object.

In order to stick with the same principles as discussed above (such as not having a huge-mess of compile dependencies), I'd like to have individual objects registering themselves with their associated container objects. So, at start-up, I'd like to have MeshManager register itself with ResourceManager, and MeshLoadMilkshape register itself with MeshManager. Only ResourceManager needs to present itself to the outside world.

So - registering MeshManager with ResourceManager is just an example of all the discussion above. But - how to register MeshLoadMilkshape with MeshManager? Do you need to have MeshManager as a singleton? Can you even have a container of singleton pointers (doesn't the base class get in the way)? Is there anyway to ensure that all registrations take place, and in the correct order?

So - if anyone has any thoughts, would be much appreciated. Meanwhile, I'll carry on tapping away and see if I can find a working solution to this.

Thanks,
Jim.

Share this post


Link to post
Share on other sites
Don't know if anyone is interested, but thought I'd post my answer to my own question. It's quick and dirty code, but allows registration of multiple layers of objects, thereby removing compile-time dependencies etc. Yes, there's currently a memory leak, but that should be straightforward to get rid of. Yes, the code is horrid - but you should get the idea.


#include <string>
#include <map>
#include <iostream>

#include "singleton.h"

class MeshLoader
{
public:
virtual void LoadMesh(const std::string& name) = 0;
};


class MeshLoadMD3 : public MeshLoader
{
public:
virtual void LoadMesh(const std::string& name)
{
std::cout << "Loading MD3 Mesh...\n";
}
};


class MeshLoadMilkshape : public MeshLoader
{
public:
virtual void LoadMesh(const std::string& name)
{
std::cout << "Loading Milkshape Mesh...\n";
}
};


class IndividualResourceManager
{
public:
IndividualResourceManager() {}
virtual void LoadMesh(const std::string& name) = 0;
};


class MeshManager : public IndividualResourceManager
{
public:
MeshManager() {}
virtual void LoadMesh(const std::string& name)
{
std::map<std::string, MeshLoader*>::iterator it = theLoaders_.find(name);
it->second->LoadMesh(name);
}
bool RegisterLoader(const std::string& name, MeshLoader* theLoader)
{
return theLoaders_.insert(std::make_pair(name, theLoader)).second;
}
private:
std::map<std::string, MeshLoader*> theLoaders_;
};


typedef Loki::SingletonHolder<MeshManager> SingleMeshManager;


class ResourceManager
{
public:
ResourceManager() {};
bool RegisterManager(const std::string& name, IndividualResourceManager* theManager);
void LoadMesh(const std::string& name);
private:
std::map<std::string, IndividualResourceManager*> theMap_;
};
bool ResourceManager::RegisterManager(const std::string& name, IndividualResourceManager* theManager)
{
return theMap_.insert(std::make_pair(name, theManager)).second;
}
void ResourceManager::LoadMesh(const std::string& name)
{
// Only playing with, so just returns the first element
theMap_.begin()->second->LoadMesh(name);
}


typedef Loki::SingletonHolder<ResourceManager> SingleResourceManager;


namespace
{
const bool registered = SingleResourceManager::Instance().RegisterManager("md3", &SingleMeshManager::Instance());
}


namespace
{
const bool registered1 = SingleMeshManager::Instance().RegisterLoader("md3", new MeshLoadMD3);
const bool registered2 = SingleMeshManager::Instance().RegisterLoader("milkshape", new MeshLoadMilkshape);
}


int main()
{

SingleResourceManager::Instance().LoadMesh("md3");
SingleResourceManager::Instance().LoadMesh("milkshape");

std::cin.get();

}



Well, it was an interesting learning process for me anyway!

Thanks again to those who have contributed.
Jim.

Share this post


Link to post
Share on other sites

This topic is 4732 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this