1

Trying to re-factor some infrastructure code I've decided to use inheritance to achieve polymorphic behavior between several classes. The next step is to throw them in to a container, iterate over them and DoSomething();

My current construction of the container is clumsy. Even if I use this elegant solution for the container, construction is still a mess.

vector<unique_ptr<Parent>> vec;

vec.push_back(unique_ptr<Parent>(new ChildA(var1, var2)));
vec.push_back(unique_ptr<Parent>(new ChildB(var3, var4)));
vec.push_back(unique_ptr<Parent>(new ChildC(var5, var6)));

ChildA, ChildB and ChildC are all polymorphic to one another. Var1 ... var6 may be from different types and are required for the construction of the child objects.

I would like to create this vector in several places for usage. The problem is that the amount of children may vary and hence the data the children need for construction. Trying to walk the path of stdarg.h failed since this isn't supported:

if     !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif

How would you implement a factory method which constructs this array?

UPDATE, desired solution:

vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2, var5, var6 typeInfo3);
vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2);

line 1 will create the same array as above. line 2 will reuse the same code to create a similar vector but with one less object. Typeinfo holds the information required to create the object.

This is a basic solution, a more advanced one will enforce the argument list in compile time. for example the following call to the function doesn't make sense:

vec = CreatePolyVec(var1, var2,typeinfo1, var3, var4, typeinfo2, var5);

not enough parameters to create the last child object.

Community
  • 1
  • 1
qballer
  • 2,033
  • 2
  • 22
  • 40

2 Answers2

4

There are two problems here, 1. How to decide what type each child will be, and 2. How to create multiple children.

To decide which child to create

This can be done at compile time or runtime. To do this at compile time you need templates.

template<class Child, class Arg1, class Arg2>
vector<unique_ptr<Parent>> CreateVec(Arg1&& arg1, Arg2&& arg2)
{
    vector<unique_ptr<Parent>> result;
    result.push_back(unique_ptr<Child>(
        new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
    return result;
}

called as follows CreateVec<MyChild>(myArg1, myArg2).

If you need to decide at runtime, you can use a map of factory functions indexed by a run-time variable. Or you could use a pointer to a factory object as your run-time variable.

To create multiple children

Here you have a choice between chained functions and variadic templates.

Chained functions

This is essentially what iostreams does. Have your function which creates a vector and adds a single child return an object which allows you to add a second child, and returns itself allowing you to continue.

The problem here is that you want the function to return the vector, so it can't also return another object. You could use a conversion operator to get the vector, or an explicit function, but probably easiest is to create the vector first, and just use the functions to add the children.

class AddChildren
{
    vector<unique_ptr<Parent>>& m_vector;
public:
    explicit AddChildren(vector<unique_ptr<Parent>>& theVector)
        : m_vector(theVector) {}

    template<class Child, class Arg1, class Arg2>
    AddChildren& add(Arg1&& arg1, Arg2&& arg2)
    {
        m_vector.push_back(unique_ptr<Child>(
            new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
        return *this;
    }
};

used as follows:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
    .add<ChildA>(var1, var2)
    .add<ChildB>(var3, var4)
    .add<ChildC>(var5, var6);

If you are using a run-time method of choosing the type you can use operator() and have it look like this:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
    (childAType, var1, var2)(childBType, var3, var4)(childCType, var5, var6);

(This can also be done with compile-time selection of type by using a dummy object of a specific type-selector type for each child type as a parameter.)

Using variadic templates

Use a variadic template to peel off three parameters at a time, and add a child object.

void addChildren(vector<unique_ptr<Parent>>& theVector)
{}

template<class FirstChild, class FirstArg1, class FirstArg2, class... Rest>
void addChildren(vector<unique_ptr<Parent>>& theVector,
    FirstChild childtype, FirstArg1&& arg1, FirstArg2&& arg2, Rest&&... rest)
{
    addChild(theVector, childtype,
        std::forward<Arg1>(arg1), std::forward<Arg2>(arg2));
    addChildren(theVector, std::forward<Rest>(rest)...);
}

template<class... Args>
vector<unique_ptr<Parent>> CreateVec(Args&&... args)
{
    vector<unique_ptr<Parent>> result;
    addChildren(result, std::forward<Args>(args)...);
    return result;

}

I assume here the existence of a function addChild which can add a child given its type (as a parameter) and its arguments.

The main problem with this is that VS2012 doesn't have variadic templates. There are two ways to simulate variadic templates. 1. Write a single function which takes the maximum number of parameters you might need, and defaults most of them to some known type which you can take to mean "not present". 2. Write out as many overloads as you think you will need.

If you know you will never need more than say ten child objects, the second option is actually perfectly feasible -- you only need to write them once and it's probably less than 150 lines of code. Alternatively you can use Boost.Preprocessor to generate the functions, but that's a whole new level of complexity.

ymett
  • 2,425
  • 14
  • 22
2

With the November 2012 compiler update, VS 2012 supports variadic templates, so the following should work (it's not quite the syntax you're after, but I think it's pretty close):

struct VectorCreator
{
  explicit VectorCreator(std::vector<Parent> &vec)
    : vec_(vec)
  {}

  template <typename Type, typename... ArgType>
  VectorCreator& create(ArgType&&... arg)
  {
    vec_.push_back(std::unique_ptr<Parent>(new Type(std::forward<ArgType>(arg)...));
    return *this;
  }

private:
  std::vector<Parent> &vec_;
};

Use like this:

std::vector<Parent> vec;
VectorCreator(vec).create<ChildA>(var1, var2).create<ChildB>(var3, var4).create<ChildC>(var5, var6);
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455