Skip to main content
GameDev.net gamedev.net
🔒 Locked

[C++] covariant return types and factory pattern

Started by momotte Dec 18, 2007 at 9:19 AM 7 replies 3.5k views
Original Post
momotte
momotte
Hi! I'm having a little problem, and after some googleing, I can't seem to find a decent solution... (btw, I'm new here, and not sure if this belongs in the beginners forum or not, feel free to move it there if it does (?)) I've got a basic factory / instance design for a set of classes. for example:

class	CAnimalFactory;	// forward declare... that works OK for the base class..

class	CAnimalInstance
{
private:
public:
	CAnimalInstance() {}
	~CAnimalInstance() {}
	virtual const CAnimalFactory	*ParentFactory() const { return NULL; }
};

class	CAnimalFactory
{
protected:
	virtual CAnimalInstance	*VirtualInstanciate();
public:
	CAnimalFactory() {}
	~CAnimalFactory() {}
	CAnimalInstance	*Instanciate() { return VirtualInstanciate(); }
};

now, I'd like to specialize the two virtual functions "VirtualInstanciate()" and "ParentFactory()", making their return type covariant:

class	CDuckFactory;	// not enough, we need derivation information too
class	CDuckInstance : public CAnimalInstance
{
private:
	const CDuckFactory	*m_ParentFactory;
public:
	CDuckInstance() {}
	~CDuckInstance() {}
	virtual const CDuckFactory	*ParentFactory() const { return m_ParentFactory; }
};

class	CDuckFactory : public CAnimalFactory
{
protected:
	virtual CDuckInstance	*VirtualInstanciate();
public:
	CDuckFactory() {}
	~CDuckFactory() {}
};

however, simply forward-declaring "CDuckFactory" doesn't cut it, as it would seem the compiler needs derivation information to be sure the return type is actually covariant (which seems pretty logical), and it doesn't look too happy when given something like: "class CDuckFactory : public CAnimalFactory;" the easy way would be to ignore co-variance, but it's a bit annoying, and not very user-friendly for the users of these classes (and pretty ugly, having to carry casts all over the place...) in the worst case scenario I'll just go with not using covariance for the "VirtualInstanciate" method, but still... isn't there a clean way to do this ? [Edited by - momotte on December 18, 2007 6:08:06 PM]
Nitage
Nitage
The bad news is that you have to provide two function implementations for each factory class, the good news is that it behaves exactly like covariant return types from the perspective of a user:

class	CAnimalFactory{protected:  //Note that this is not virtual  CAnimalInstance* VirtualInstanciate()  {    return virtualInstanciateImpl();  }private:  virtual CAnimalInstance* virtualInstanciateImpl()=0;};class CDuckFactory : public CAnimalFactory{protected:  //NOTE: This shadows CAnimalFactory::VirtualInstanciate - it doesn't override it.  CDuckInstance* VirtualInstanciate();private:  //This always returns a CAnimalInstance*, regardless of the type returned by VirtualInstanciate()  virtual CAnimalInstance* virtualInstanciateImpl();};//In CDuckFactory.cppCDuckInstance* CDuckFactory::VirtualInstanciate();{  CDuckInstance* newInstance = virtualInstanciateImpl();  assert(dynamic_cast<CDuckInstance*>(newInstance));  return static_cast<CDuckInstance*>(newInstance);}



BTW, it's spelt instantiate.

[Edited by - Nitage on December 19, 2007 3:52:06 AM]
Antheus
Antheus
Quote:
Original post by Zahlman
Careful there.


Agreed...


The "library":
template < class BaseType >struct BaseConstructor {  virtual BaseType * create() = 0;};template < class BaseType, class ConcreteType >struct TypedConstructor : public BaseConstructor<BaseType> {  virtual BaseType * create() { return new ConcreteType(); }};template < typename KeyType, class BaseType >class Factory {public:  BaseType * create( KeyType &key ) { return types[key]->create(); }protected:  template < class ConcreteType >  void install< KeyType &key ) { types[key] = new TypedConstructor<BaseType, ConcreteType>(); }private:  std::map<KeyType, BaseType *> types;};


Definition of a factory for a given type:
class AnimalFactory : public Factory<int, Animal> {public:  AnimalFactory() {    install<Cat>(0);    install<Dog>(1);    install<Cow>(2);  }};


I really don't see why factories need to be so incredibly complicated, why each class you put in needs to know about the concept, why so many definitions, or intrusive methods.

And returning a specific type instead of base type pointer goes against the concept of factories - negating their purpose altogether.

Of course, someone will point out that above design is great for a singleton. It works as singleton. It can be, doesn't need to be - you can make as many as you want, or keep a single instance.

The only downside might be the need to register all the instances. Well, I don't really have a problem with that, there's very little to be gained from self-registration, especially in C++.
momotte
momotte
thanks for your answers!

but.. erm..
I shouldn't have explained it that way.

Nitage> actually, the example I posted already does that. your "VirtualInstantiate" is my "Instantiate", and your "virtualInstanciateImpl" is my "virtualInstantiate".

the actual problem wasn't there...
and the factory thing is in fact, a detail, I thought it would make things clearer but it makes them more complex, for no reason.

here...
if you have two classes A and B, with A declared before B. you can have pointers to A in B without problems, and you can have pointers to B in A as long as you forward-declare B before the declaration of A.

class B;class A{  B *ptr;};class B{  A *ptr;}


(btw, how do you insert these nice-looking code snippets? [ code ] just seems to insert a
 tag :( )
EDIT: ah.. got it... didn't see the top-right 'faq' link...

now, this works fine.
but it won't work with covariant return-types, because the compiler seems to need information about the derivations:

<!--STARTSCRIPT--><!--source lang="cpp"-->
class B;class A{  virtual B *func();};class B{  virtual A *func();};


this works fine too, however, this doesn't:

class D;class C : public A{  virtual D *func();};class D : public B{  virtual C *func();};


because apparently, writing a forward-declare like
"class D : public B;"
isn't valid C++ (or is there another syntax?)

so basically, I was wondering if there was a way to overcome that, or if it just can't be done in C++ ?


Zahlman> I read his "joel on software" book.. awesome read, I didn't know of this article, very funny :D (and an interesting image). the factory example I gave above wasn't a real world case, but thanks for the advice :)

[Edited by - momotte on December 18, 2007 6:31:04 PM]
SiCrane
SiCrane
Basically, the answer is no. In order for a class type to be a valid covariant return type, it needs to be complete at the point of declaration of the derived class member function or be the derived class being defined.
momotte
momotte
damned... ok.
I guess I'll have to find something else then :/
Nitage
Nitage
Quote:

Nitage> actually, the example I posted already does that. your "VirtualInstantiate" is my "Instantiate", and your "virtualInstanciateImpl" is my "virtualInstantiate".

No it's not. The 'virtualInstanciateImpl' function always returns a CAnimalInstance* - it doesn't use covariant return types. The 'VirtualInstantiate' function isn't virtual - derived class versions shadow the function in the base class. Seeing as covariant return types aren't being used in my example, a simple forward declaration is sufficient.

The others are correct that you're over complicating things. However the technique I showed you is well worth knowing about - it can simulate covariance in situations where it isn't directly supported (which can be very useful when dealing with smart pointers).


I've edited the original example to add comments showing what's going on
momotte
momotte
aah. ok I get it.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.