Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write templated factory function for both std::shared_ptr and std::unique_ptr

I often write factory which have a signature similar to the following:

std::unique_ptr<AbstractType> createUnique(IDType id);
std::shared_ptr<AbstractType> createShared(IDType id);

Where the former would do something like:

switch (id)
{
case kID1:
    return std::make_unique<Type1>();
}

And the latter

switch (id)
{
case kID1:
    return std::make_shared<Type1>();
}

Well, the only different between the two functions is the "make" function used (make_shared or make_unique) and the return type (shared_ptr or unique_ptr). This leads to code duplication.

I am wondering how I could write a templated create function which accepts the pointer type and "make" function type and uses those instead. Something like:

template <typename PTR, typename MAKE>
PTR create(IDType id)
{
    switch (id)
    {
    case kID1:
        return MAKE<Type1>();
    }
}

I am aware that the above is not valid code.

Also, I am aware that a std::shared_ptr could be created from an std::unique_ptr and visa versa, but I this factory would be used in different applications where one might use the shared_ptr and the other might use the unique_ptr. This way, the code is re-usable but also effecienct for the particular use-case.

like image 726
Patrick Wright Avatar asked Dec 12 '25 11:12

Patrick Wright


2 Answers

My advice, only have a single return type of std::unique_ptr<AbstractType>. If you do that then you can use the function with std::unique_ptr and std::shared_ptr like

auto my_ptr = createInterface(id);

which has my_ptr being a unique_ptr or

std::shared_ptr<AbstractType> my_ptr = createInterface(id);

and now the returned unique_ptr is converted into a shared_ptr.

like image 83
NathanOliver Avatar answered Dec 14 '25 23:12

NathanOliver


To avoid loosing use of std::make_shared which optimizes use of memory, I would approach this issue this way:

class AbstractType {
public:
    virtual ~AbstractType() = default;

    virtual int f() const = 0;
};

class Foo : public AbstractType {
public:
    int f() const override
    {
        return 1;
    }
};

class Bar : public AbstractType {
public:
    int f() const override
    {
        return 2;
    }
};

enum class IDType {
    Foo,
    Bar,
};

class Factory {
public:
    std::unique_ptr<AbstractType> createUnique(IDType id);
    std::shared_ptr<AbstractType> createShared(IDType id);

private:
    template <typename MakePtr>
    auto createUniversal(IDType id);
};

//======================
class WrapMakeUnique {
public:
    template <typename Base, typename T, typename... Args>
    static auto make(Args... args) -> std::unique_ptr<Base>
    {
        return std::make_unique<T>(std::forward<Args>(args)...);
    }
};

class WrapMakeShared {
public:
    template <typename Base, typename T, typename... Args>
    static auto make(Args... args) -> std::shared_ptr<Base>
    {
        return std::make_shared<T>(std::forward<Args>(args)...);
    }
};

template <typename MakePtr>
auto Factory::createUniversal(IDType id)
{
    switch (id) {
    case IDType::Foo:
        return MakePtr::template make<AbstractType, Foo>();
    case IDType::Bar:
        return MakePtr::template make<AbstractType, Bar>();
    }
    throw std::invalid_argument { "IDType out of range" };
}

std::unique_ptr<AbstractType> Factory::createUnique(IDType id)
{
    return createUniversal<WrapMakeUnique>(id);
}

std::shared_ptr<AbstractType> Factory::createShared(IDType id)
{
    return createUniversal<WrapMakeShared>(id);
}

https://godbolt.org/z/9f1ddj9zo

like image 42
Marek R Avatar answered Dec 15 '25 01:12

Marek R