Porting Macro/template code from C++ into C#

Started by
4 comments, last by gameXcore 12 years, 10 months ago
Hi all,

I am currently porting some code from C++ into C# and have come a little stuck with recreating my template factory class. I was wandering if anyone could give me any hints or pointers as to where I would start. First I'll post the C++ code, then I will post a description and use-case example.

Code:
FactoryTemplate.h

#include <map>

#include "../Utilities/Logger.h"

namespace XF
{
namespace Utilities
{

#define XF_FACTORY_JOIN_MACRO( X, Y) XF_FACTORY_DO_JOIN( X, Y )
#define XF_FACTORY_DO_JOIN( X, Y ) XF_FACTORY_DO_JOIN2(X,Y)
#define XF_FACTORY_DO_JOIN2( X, Y ) X##Y

#define XF_FACTORY_CLASS_NAME XF_FACTORY_JOIN_MACRO(Factory, XF_FACTORY_NUM_ARGS)
#define XF_FACTORY_MAKER_BASE_NAME XF_FACTORY_JOIN_MACRO(MakerBase, XF_FACTORY_NUM_ARGS)
#define XF_FACTORY_MAKER_NAME XF_FACTORY_JOIN_MACRO(Maker, XF_FACTORY_NUM_ARGS)

#if XF_FACTORY_NUM_ARGS > 0
#define XF_FACTORY_SEPERATOR ,
#else
#define XF_FACTORY_SEPERATOR
#endif

template <typename Type, typename Key XF_FACTORY_SEPERATOR XF_FACTORY_TEMPLATE_ARGS>
class XF_FACTORY_CLASS_NAME
{
private:
template <typename Type, typename Key XF_FACTORY_SEPERATOR XF_FACTORY_TEMPLATE_ARGS>
class XF_FACTORY_MAKER_BASE_NAME
{
public:
typedef typename Type BaseType;
typedef typename Key KeyType;

public:
XF_FACTORY_MAKER_BASE_NAME(const KeyType& k):key(k) {}

const KeyType &GetKey() {return key;}

virtual BaseType* Create(XF_FACTORY_ARG_PARAMS) = 0;
virtual std::string GetMakeTypeName() = 0;
private:
KeyType key;
};

template <typename BaseType, typename Make, typename Key XF_FACTORY_SEPERATOR XF_FACTORY_TEMPLATE_ARGS>
class XF_FACTORY_MAKER_NAME : public XF_FACTORY_MAKER_BASE_NAME<BaseType, Key XF_FACTORY_SEPERATOR XF_FACTORY_ARGS>
{
public:
typedef typename Make MakeType;

XF_FACTORY_MAKER_NAME(const KeyType &key) : XF_FACTORY_MAKER_BASE_NAME(key) {}

BaseType* Create(XF_FACTORY_ARG_PARAMS)
{
return new MakeType(XF_FACTORY_PARAMS);
}

std::string GetMakeTypeName(){return typeid(MakeType).name();}
};

typedef typename XF_FACTORY_MAKER_BASE_NAME<Type, Key XF_FACTORY_SEPERATOR XF_FACTORY_TEMPLATE_ARGS> MakerBaseType;

public:
typedef typename Type ObjectType;
typedef typename Key KeyType;

XF_FACTORY_CLASS_NAME(){}

~XF_FACTORY_CLASS_NAME()
{
UnregisterTypes();
}

template <typename CreationType>
void RegisterType(const KeyType &key)
{
MakerCollectionIt it(makers.find(key));

if(it == makers.end())
{
MakerBaseType *newMaker
= new XF_FACTORY_MAKER_NAME<ObjectType, CreationType, KeyType XF_FACTORY_SEPERATOR XF_FACTORY_TEMPLATE_ARGS>(key);
makers.insert(std::make_pair(key, newMaker));

LOG_INFO << "Type registered: ([" << typeid(CreationType).name() << "] - '" << key << "')";
}
else
{
LOG_WARNING << "Attempting to register type using duplicate key ('" << key << "')" << Logging::LnBreak
<< " Original: [" << it->second->GetMakeTypeName() << "]" << Logging::LnBreak
<< " New: [" << typeid(CreationType).name() << "]" << Logging::LnBreak
<< " Request Ignored!";
}
}

void UnregisterTypes()
{
for(MakerCollectionIt it(makers.begin()); it != makers.end(); ++it)
{
delete it->second;
}

makers.clear();
}

ObjectType* Create(const KeyType &key XF_FACTORY_SEPERATOR XF_FACTORY_ARG_PARAMS)
{
MakerCollectionIt it(makers.find(key));

return it == makers.end() ? NULL : it->second->Create(XF_FACTORY_PARAMS);
}

protected:
typedef std::map<KeyType, MakerBaseType*> MakerCollection;
typedef typename MakerCollection::iterator MakerCollectionIt;
MakerCollection makers;
};
}
}

#undef XF_FACTORY_JOIN_MACRO
#undef XF_FACTORY_DO_JOIN
#undef XF_FACTORY_DO_JOIN2

#undef XF_FACTORY_CLASS_NAME
#undef XF_FACTORY_SEPERATOR
#undef XF_FACTORY_MAKER_NAME
#undef XF_FACTORY_MAKER_BASE_NAME


Factory.h

#ifndef XF_UTILITIES_FACTORY_H
#define XF_UTILITIES_FACTORY_H


#define XF_FACTORY_NUM_ARGS 0
#define XF_FACTORY_TEMPLATE_ARGS
#define XF_FACTORY_ARGS
#define XF_FACTORY_ARG_PARAMS
#define XF_FACTORY_PARAMS
#include "FactoryTemplate.h"
#undef XF_FACTORY_NUM_ARGS
#undef XF_FACTORY_ARGS
#undef XF_FACTORY_TEMPLATE_ARGS
#undef XF_FACTORY_ARG_PARAMS
#undef XF_FACTORY_PARAMS

#define XF_FACTORY_NUM_ARGS 1
#define XF_FACTORY_TEMPLATE_ARGS typename A1
#define XF_FACTORY_ARGS A1
#define XF_FACTORY_ARG_PARAMS A1 a1
#define XF_FACTORY_PARAMS a1
#include "FactoryTemplate.h"
#undef XF_FACTORY_NUM_ARGS
#undef XF_FACTORY_ARGS
#undef XF_FACTORY_TEMPLATE_ARGS
#undef XF_FACTORY_ARG_PARAMS
#undef XF_FACTORY_PARAMS

#define XF_FACTORY_NUM_ARGS 2
#define XF_FACTORY_TEMPLATE_ARGS typename A1, typename A2
#define XF_FACTORY_ARGS A1, A2
#define XF_FACTORY_ARG_PARAMS A1 a1, A2 a2
#define XF_FACTORY_PARAMS a1, a2
#include "FactoryTemplate.h"
#undef XF_FACTORY_NUM_ARGS
#undef XF_FACTORY_ARGS
#undef XF_FACTORY_TEMPLATE_ARGS
#undef XF_FACTORY_ARG_PARAMS
#undef XF_FACTORY_PARAMS

#endif


The user #includes Factory.h to use the factory.

The above code will generate 3 templates Factory0<ReturnType>, Factory2<ReturnType, ParamType1> and Factory2<ReturnType, ParamType1, ParamType2> which can then be used to create factory's templated to any type the user requires.

Use case:

class A {};
class AA : public A {}
class AB : public A {}

class B {B(int, float){}};
class BA : public A{BA(int, float) : B(){}};

Factory0<A> f1;
Factory2<B, int, float> f2;

f1.RegisterType<AA>("AA");
f1.RegisterType<AB>("AB");
f2.RegisterType<BA>("BA");

A *a1 = f1.Create("AA");
A *a2 = f1.Create("AB");

B *b1 = f2.Create("BA", 1, 2.0f);
B *b2 = f2.Create("BA", 4, 7.8f);


With no direct replacement for the way I use #define in the above code to generate the class names, as well as fill in the argument types/ parameters etc I'm completely stumped on what mechanisms are available.

Any help or pointers would be greatly appreciated.
Thanks,
Scott
Game development blog and portfolio: http://gamexcore.co.uk
Advertisement
Metaprogramming options in C# aren't quite as robust as in C++. A few thoughts:

1. Direct ports are often ill-advised in the best of cases. This is a fairly edge case taking advantage of C++ specific features. It's probably better to re-solve this problem in a C# style.

2. Reflection will provide mechanisms for getting type information out, but I submit that you should generally avoid that (even in C++). It will also provide mechanisms for doing inspection on types' constructors, and you can use it to auto-detect types to factory up rather than requiring explicit registration. There's also mechanisms for type/method creation if you're feeling really ambitious. But it's slow and awkward.

3. There are some standard .NET libraries to do things like this, depending on what you actually use this for. Unity is a solid IoC container. MEF is available for dynamically discovering types and factorying them up based on traits/attributes.

4. Even in C++, using template metaprogramming for simple variable length templates is kinda overkill. It's not terribly burdensome to copy/paste out 10 variants or write a quick script to generate C# code.
I havent tested but thisshould work :


public class Factory<TBase>
{
private Dictionary<string, Type> dictionary = new Dictionary<string, Type>();

public void RegisterType<TDerived>(string name) where TDerived : TBase
{
this.dictionary.Add(name, typeof(TDerived));
}

public TBase CreateInstance(string name, params object[] args)
{
return (TBase)Activator.CreateInstance(dictionary[name], args);
}
}





Use case :


public class A
{
}

public class AB : A
{
public AB(float f, int i, double d)
{ }
}

// ...

var factory = new Factory<A>();

factory.RegisterType<AB>("AB");

A ab = factory.CreateInstance("AB", 2.5f, 8, 5.2);
@Telastyn

Indeed this is one of those areas where c# lacks when you put it against C++ and I did think it was going to be bit of an ask to find a direct replacement, this is just one of those bits of code which I realized I use a lot and it would have been nice to have it around with the familiar interface!

@a_loho

I also thought of that option however I was under the impression that in order to use
params object[] args


[font="Arial"]for the variable arguments that I would also have to have the objects take in params object[] as their sole constructor arguments, as opposed to actual types.[/font]
Game development blog and portfolio: http://gamexcore.co.uk

I also thought of that option however I was under the impression that in order to use
params object[] args

[font="Arial"]for the variable arguments that I would also have to have the objects take in params object[] as their sole constructor arguments, as opposed to actual types.[/font]


The Activator.CreateInstance does take a params object[] as argument but it internally tries to find the constructor that best match the arguments passed in. So you dont have to make a constructor with a params object[] args. However there is a performance cost since it will use reflection to find that constructor.
Ahh I completely didn't spot the Activator line, I must have read it as simply creating the type, then by the looks of it that looks perfect for what I'm after. As for the performance I can use this for the game and then if it ever becomes a bottleneck I can simply replace it with a direct factory dealing with that specific type.

Thanks a lot.
Scott
Game development blog and portfolio: http://gamexcore.co.uk

This topic is closed to new replies.

Advertisement