8

I want to create something like an universal factory method - look at this one:

template <class BaseType>
class Factory {
  public:
    template <class ... Args>
    static BaseType* Create(const Args& ... args) {
      return new DerivedType(args ...);
    }
};

Where the DerivedType is some other type derived from BaseType and defined in a different place.

The problem is with storing DerivedType. I want to do it, for example, like this:

void f() {
  // Derived type may have more than one constructor,
  // that's why I suggest using of the variadic templates.
  BaseType* ptr1 = Factory<BaseType>::Create("abc", 5, 10.);
  BaseType* ptr2 = Factory<BaseType>::Create();
  ...
}

...

Factory<BaseType>::SetType<MyDerivedType>();
f();

Factory<BaseType>::SetType<YourDerivedType>();
f();

I can set different derived types, but all of them are known at compile-time. I can't think of an appropriate technique to do this.

Question: Can you advise one?


The rationale of doing this (thus, the original problem, if someone suggests the question is it-self the XY problem) - is an ability to unit-test some tricky parts of code. For example, if I have a code:

...
Shuttle* shuttle1 = new ShuttleImpl("Discovery", Destination::Moon);
Shuttle* shuttle2 = new ShuttleImpl();
...

And I don't want to really construct the shuttle each time I run unit-tests:

class Shuttle: public Factory<Shuttle> { ... }
...
Shuttle* shuttle1 = Shuttle::Create("Discovery", Destination::Moon);
Shuttle* shuttle2 = Shuttle::Create();
...

So, in the unit-test I can just do: Shuttle::SetType<TestShuttle>();.

There may be more "testable" classes, that's why I need an universal factory for all of them:

class Car: public Factory<Car> { ... }
class Driver: public Factory<Driver> { ... }
...
abyss.7
  • 13,882
  • 11
  • 56
  • 100
  • I'm guessing some form of type erasure and an interface mimicking an `std` allocator would be the direction to explore, but no idea whether it's actually possible. – Angew is no longer proud of SO Jan 28 '14 at 08:03
  • Why can't you use a second template parameter, e.g.: `template class Factory;` ? – CouchDeveloper Jan 28 '14 at 09:57
  • @CouchDeveloper - because there can be more than one `Derived` in the code: i.e. I may test the same code with a complete mock, or with a special test class, that checks the number of methods invocations. – abyss.7 Jan 28 '14 at 10:01
  • You cannot have the _same_ code which requires a different implementation. However, a _template_ allows you to have the _same_ template which instantiates to different code. Exactly what you are looking for, unless I don't understand your problem. – CouchDeveloper Jan 28 '14 at 10:04
  • @CouchDeveloper - then propose your solution as a sample code, and I will try to show you, if you misunderstand my problem or not. – abyss.7 Jan 28 '14 at 10:11
  • OK, added a possible solution ;) – CouchDeveloper Jan 28 '14 at 10:21
  • Looks like you need CRTP here. – Muxecoid Jan 28 '14 at 11:43

2 Answers2

1

Not a complete answer, but your class template's Create static function template should be:

template <class BaseType>
class Factory {
  public:
    template <class... Args>
    static BaseType* Create(Args&&... args) {
      return new DerivedType(std::forward<Args>(args)...);
    }
};

See also When to use std::forward to forward arguments?


Edit:

Why does a second template argument not solve your problem?

For example:

template <class Base, class Derived>
class Factory {
  public:
    template <class... Args>
    static Base* Create(Args&&... args) {
      return new Derived(std::forward<Args>(args)...);
    }
};

Instead of

Factory<BaseType>::SetType<MyDerivedType>();
f();

Factory<BaseType>::SetType<YourDerivedType>();
f();

you may write:

Factory<MyBase, MyDerived1> factory1;
Factory<MyBase, MyDerived2> factory2;

auto object1 = factory1::Create(1, "a");
auto object2 = factory2::Create(1.2, "abc");
Community
  • 1
  • 1
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • The second template argument doesn't solve the problem, since I *must* not change the original call to `Create()`. I.e. I have the code symbolized by the function `f()` in my sample, that is immutable, and I have the unit-test, which has to alter some objects inside `f()`. It would be meaningless otherwise, if I have to change the original code to each invocation with different `Derived`s, like `f1()`, `f2()`, etc. My sample code is trivial, while the real program is much more complicated. – abyss.7 Jan 28 '14 at 10:32
  • Also, the `object1` and `object2` in your answer should use the same derived type at once, with respect to my sample code. – abyss.7 Jan 28 '14 at 10:34
  • @abyss.7 You should realize that if you must not change the code of `Create` which has been created from a template - there's no way to inject a mock. You have to do such things at runtime. This, however, will require a completely different design. – CouchDeveloper Jan 28 '14 at 10:42
  • I don't complain about doing it at runtime, but how it implies, that my design is wrong? Moreover, I have solved this problem for the case with a single default constructor, using this design. If you can advise a better design, I would appreciate it. – abyss.7 Jan 28 '14 at 10:56
  • @abyss.7 You cannot just "switch" the type `DerivedType` as in your example (which is a template) to get a different type. You can't set a "type" at runtime, too. You can set "objects" at runtime. That may mean a function, which creates your object. But then, your Factory method doesn't make much sense, since it would fully _delegate_ the object creation to a function defined "outside". Also, parameters can't be set via a variadic template pack. You possibly may use a tuple. This all means, your originally design cannot even remotely cope with this. You need something totally different. – CouchDeveloper Jan 28 '14 at 11:04
0

IF your factory knows all possible derived classes, following may help:

// get_index<T, T1, .., TK-1, T, Ts...> is std::integral_constant<std::size_t, K>
template <typename T, typename ... Ts> struct get_index;

template <typename T, typename ... Ts>
struct get_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename Tail,  typename ... Ts>
struct get_index<T, Tail, Ts...> :
        std::integral_constant < std::size_t, 1 + get_index<T, Ts...>::value > {};

template <typename Base, typename...Deriveds>
struct Factory
{
private:
    template <typename Derivated, typename...Ts>
    static constexpr Base* allocator(Ts&&...args)
    {
        return new Derivated(std::forward<Ts>(args)...);
    }

    template <typename...Ts>
    static constexpr std::array<Base*(*)(Ts&&...), sizeof...(Deriveds)>
    array_alloc()
    {
        return std::array<Base*(*)(Ts&&...), sizeof...(Deriveds)>
            {{ &allocator<Deriveds, Ts&&...>... }};
    }

public:
    template <typename...Ts>
    static Base* create(Ts&&...args)
    {
        return array_alloc<Ts...>()[active](std::forward<Ts>(args)...);
    }

    template <typename Derived>
    static void setType()
    {
        active = get_index<Derived, Deriveds...>::value;
    }

private:
    static std::size_t active;
};

template <typename Base, typename...Deriveds>
std::size_t Factory<Base, Deriveds...>::active = 0;

And use it like:

class Base {};

struct D1 : Base {
    D1() {std::cout << "D1" << std::endl;}
    D1(int a, int b) {}
};

struct D2 : Base {
    D2() {}
    D2(int a, int b) { std::cout << "D2(" << a << ", " << b << ")" << std::endl; }
};

int main(int argc, char *argv[])
{
    typedef Factory<Base, D1, D2> BaseFactory; // default to D1

    Base* b1 = BaseFactory::create(); // D1()
    BaseFactory::setType<D2>();
    Base* b2 = BaseFactory::create(42, 53); // D2(42, 53)

    delete b2;
    delete b1;

    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302