How to create pointer to different instantiation of template class

Viewed 166

In making my first experiments with Abstract Factory in C++(while reading Modern C++ Design - A. Alexandrescu - part 9) i have a question. If classes hierarchy looks like this:

struct B {}; 

struct D1 :public B {};
struct DD1 : public D1 {};
struct DD2 : public D1 {};

struct D2 :public B {};
struct DD3 : public D2 {};
struct DD4 : public D2 {};

I have this abstract factory code:


//abstract factory version 1

struct AbstractFactoryImpl
{
    virtual D1* CreateD1() = 0;
    virtual D2* CreateD2() = 0;
};

struct AbstractFactory : public AbstractFactoryImpl
{
    virtual D1* CreateD1() { return new D1; };
    virtual D2* CreateD2() { return new D2; };
};

template<class ... Ts>
struct ConcreteFactory :public AbstractFactory
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    virtual D1* CreateD1()override
    {
        static_assert(std::is_base_of_v<D1, T1>);
        return new T1;
    };
    virtual D2* CreateD2()override
    {
        static_assert(std::is_base_of_v<D2, T2>);
        return new T2;
    };
};

And using it in client code like this.

//version 1
    AbstractFactory* pFactory;
    pFactory = new ConcreteFactory<DD1, DD3>;
    D1* pD1 = pFactory->CreateD1();
    D2* pD2 = pFactory->CreateD2();

    pFactory = new ConcreteFactory<DD2, DD4>;
    pD1 = pFactory->CreateD1();
    pD2 = pFactory->CreateD2();

It works what i want and looks like a big part of examples in internet.

But in this version of abstract factorys if i want to add more classes in heirarchy(D3,DD5,DD6...) i must create too much code manually so i want to make code more generic.

//abstract factory version 2
template<typename T>
struct AbstractFactoryImpl_
{
    virtual T* Create() = 0;
};

template<typename T>
struct AbstractFactory_ :public AbstractFactoryImpl_<T>
{
    virtual T* Create() override
    {
        return new T;
    }
};

template<class ...Ts>
struct ConcreteFactory_ : public AbstractFactory_<Ts>...
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    template<class T>
    T* Create()
    {
        if constexpr (std::is_base_of_v<T, T1>)return AbstractFactory_<T1>::Create();
        else if constexpr (std::is_base_of_v<T, T2>)return AbstractFactory_<T2>::Create();

    }
};

And it works in client code like this:

        //version 2
    //AbstractFactory* pFactory; -- can`t use because AbstractFactory in version 2 is template class.
    auto pFactory_1 = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_1->Create<D1>();
    D2* pD2_ = pFactory_1->Create<D2>();

    auto pFactory_2 = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_2->Create<D1>();
    pD2_ = pFactory_2->Create<D2>();

So it works but i must create two different pointers(pFactory_1,pFactory_2) to different instances of ConcreteFactory_. This is what not expected from Abstract Factory. In first version it was ok because of virtual enheritance to call Create on Base class pointer. But here i have template base class. So i cant call on it pointer Create(). So the question is how to make pointer or something else may be std::any o std::variant to make it possible in this Abstract Factory class design? The client code i wanna make to work is same like first version.

    TYPE* pFactory_ = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_->Create<D1>();
    D2* pD2_ = pFactory_->Create<D2>();

    auto pFactory_ = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_->Create<D1>();
    pD2_ = pFactory_->Create<D2>();

Full code: [https://cppinsights.io/s/552bbe01]

2 Answers

With some changes you might have:

// Way to "pass" Type.
template <typename T> struct Tag{};

template <typename T>
struct AbstractFactory
{
    virtual ~AbstractFactory() = default;
    virtual std::unique_ptr<T> Create(Tag<T>) const = 0;
};

template <typename ... Ts>
struct AbstractFactories : virtual AbstractFactory<Ts>...
{
    using AbstractFactory<Ts>::Create ...;
};

template <typename T, typename T2>
struct ConcreteFactory : virtual AbstractFactory<T>
{
    std::unique_ptr<T> Create(Tag<T>) const override { return std::make_unique<T2>(); }
};

template <typename Base, typename ...Ts>
struct ConcreteFactories;

template <typename ... Ts1, typename ...Ts2>
struct ConcreteFactories<AbstractFactories <Ts1...>, Ts2...> : AbstractFactories <Ts1...>, ConcreteFactory<Ts1, Ts2>...
{
    using ConcreteFactory<Ts1, Ts2>::Create ...;
};

With usage

void Test(AbstractFactories<D1, D2>& factory)
{
    std::unique_ptr<D1> d1 = factory.Create(Tag<D1>{});
    std::unique_ptr<D2> d2 = factory.Create(Tag<D2>{});
    // ...
}

int main()
{
    ConcreteFactories<AbstractFactories<D1, D2>, DD1, DD3> factory1;
    ConcreteFactories<AbstractFactories<D1, D2>, DD2, DD4> factory2;
    
    Test(factory1);
    Test(factory2);
}

Demo

A simple solution would be to add an alias into your concrete classes.

The advantage of this solution is that it keeps the syntax you wanted:

struct B {}; 

struct D1 : B {};
struct DD1 : D1 {
    using overrides = D1;
};
struct DD2 : D1 {
    using overrides = D1;
};

struct D2 : B {};
struct DD3 : D2 {
    using overrides = D2;
};
struct DD4 : D2 {
    using overrides = D2;
};

Then, simply change your AbstractFactory_ type to this:

template<typename T>
struct AbstractFactoryImpl_
{
    virtual std::unique_ptr<T> Create() = 0;
};

template<typename T>
struct AbstractFactory_ : AbstractFactoryImpl_<typename T::overrrides>
{
    auto Create() -> std::unique_ptr<typename T::overrrides> override
    {
        return std::make_unique<T>();
    }
};

Using this, your code will simply work:

auto pFactory1 = ConcreteFactory_<DD1, DD3>{};
std::unique_ptr<D1> pD1_ = pFactory_.Create<D1>();
std::unique_ptr<D2> pD2_ = pFactory_.Create<D2>();

auto pFactory2 = ConcreteFactory_<DD2, DD4>{};
pD1_ = pFactory_.Create<D1>();
pD2_ = pFactory_.Create<D2>();

So the question is how to make pointer or something else may be std::any o std::variant to make it possible in this Abstract Factory class design? The client code i wanna make to work is same like first version.

Don't do that. If you use pointer and inheritance for polymorphism, I'd advise not to mix other utility. For example, if you choose to return a std::any, you will have two layer of indirection: the any class, then the pointer. And to actually use the pointer, you will have to cast it to the right type, making the inheritance based indirection useless.

If you want to return std::any, go full std::any and always cast to the right type and skip the inheritance altogether. If you want to use std::variant, remove the inheritance and skip the virtual stuff and simply put all your types in the variant.

Looking at your inheritance structure, it's indeed a deep one (multiple level of inheritance). The solution would be to have a BFactory that has a pure virtual function returning a pointer to B and make all your factories extend from it.

It will add casts in your code, almost everywhere. Also, you repeat yourself, since you have to repeat the whole inheritance structure in your factory classes.

At this point, I would simply use type erasure instead. Instead of a deep, complex hierarchy of factories, I would use a generic type, concrete, that change its behavior depending on what you construct it with. You can roll your own, but you can also use one provided by the standard library. The simplest is std::function:

struct ClassThatNeedToMakeD1 {
    std::function<std::unique_ptr<D1>()> makeD1;

    void stuff() {
        auto myD1ptr = makeD1();
    }
};

int main() {
    auto myClass = ClassThatNeedToMakeD1{
        []{ return std::make_unique<DD2>(); }
    };

    myClass.stuff();
}

This stays simple, extensible and will do what you want at the places you want. Instead of coupling the construction strategy of all the B subclasses into a hierarchy of classes, you simply assign wanted behavior at the places that need that type erased factory function.

If you don't want to use std::function, you can roll your own limited equivalent:

template<typename T>
struct AnyFactory {
    template<typename Function>
    AnyFactory(Function f) : _factory{std::make_unique<Concrete<Function>>(std::move(f))} {}

    auto operator()() const -> std::unique_ptr<T> {
        return _factory->create();
    }

private:
    struct Abstract {
        virtual ~Abstract() = default;
        virtual auto create() const -> std::unique_ptr<T> = 0;
    };

    template<typename Function>
    struct Concrete : Abstract {
        Concrete(Function c) noexcept : _create{std::move(c)} {}

        auto create() const -> std::unique_ptr<T> override {
            return _create();
        }

        Function _create;
    };

    // Copy on write. The const is very important, don't remove it.
    // If you want to remove the const, change to unique_ptr + clone function in Abstract
    std::shared_ptr<const Abstract> _factory;
};

struct ClassThatNeedToMakeD1 {
    AnyFactory<D1> makeD1;

    void stuff() {
        auto myD1ptr = makeD1();
    }
};
 
int main() {
    // Usage stays the same
    auto myClass = ClassThatNeedToMakeD1{
        []{ return std::make_unique<DD2>(); }
    };

    myClass.stuff();
}

Small suggestions:

  • Use std::unique_ptr<T> instead of T*, and std::make_unique<T>() instead of new T. I applied it in my answer.
  • When using structs, you don't need public in inheritance. Structs inherit publicly by default.
  • When you add the override keyword, don't put virtual. This is redundant.
Related