如何为 std::shared_ptr 和 std::unique_ptr 编写模板化工厂函数

问题描述 投票:0回答:3

我经常写工厂,其签名类似于以下内容:

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

前者会做类似的事情:

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

后者

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

好吧,这两个函数之间的唯一区别是使用的“make”函数(

make_shared
make_unique
)和返回类型(
shared_ptr
unique_ptr
)。这会导致代码重复。

我想知道如何编写一个模板化的

create
函数,它接受指针类型和“make”函数类型并使用它们。像这样的东西:

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

我知道上面的代码无效。

此外,我知道

std::shared_ptr
可以从
std::unique_ptr
创建,反之亦然,但我这个工厂将用于不同的应用程序,其中一个可能使用
shared_ptr
,另一个可能使用
unique_ptr
.这样,代码可重用,但对于特定用例也很有效。

c++ templates c++17 smart-pointers
3个回答
6
投票

我的建议,只有一个

std::unique_ptr<AbstractType>
的返回类型。如果你这样做,那么你可以使用
std::unique_ptr
std::shared_ptr
like

的功能
auto my_ptr = createInterface(id);

my_ptr
unique_ptr

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

现在返回的

unique_ptr
被转换成
shared_ptr
.


3
投票

为了避免丢失优化内存使用的

std::make_shared
,我会这样处理这个问题:

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


0
投票

你的通用工厂方法非常接近

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

您缺少的关键部分似乎是 (1)

MAKE
是一种类型,所以
MAKE<Type1>()
是我们可能想要跳过的语法,以及 (B) 如何使用它。

理想的使用方式是使用两个工厂类:


template<class Base>
struct make_unique_factory {
    using PTR = std::unique_ptr<Base>;
    template<class Derived, class... Args >
    std::unique_ptr<Base> create(Args&&... args ) const 
    {return std::make_unique<Derived>(std::forward<Args>(args)...);}
};
template<class Base>
struct make_shared_factory {
    using PTR = std::shared_ptr<Base>;
    template<class Derived, class... Args >
    std::shared_ptr<Base> create(Args&&... args ) const 
    {return std::make_shared<Derived>(std::forward<Args>(args)...);}
};

(我们使用类而不是方法,以便编译器更容易内联。方法参数更难内联)

然后使用类似于您所拥有的:

template <typename MAKE>
auto create(IDType id) -> typename MAKE::PTR
{
    switch (id)
    {
    case kID1:
        return MAKE{}.template create<Type1>();
    }
}
std::unique_ptr<AbstractType> createUnique(IDType id) {
    return create<make_unique_factory<AbstractType>>(id);
}
std::shared_ptr<AbstractType> createShared(IDType id) {
    return create<make_shared_factory<AbstractType>>(id);
}

http://coliru.stacked-crooked.com/a/bc3d5d2431a0b0da

你遇到麻烦的原因可能是这条线

MAKE{}.template create<Type1>()
所以我会把它分解。
MAKE{}
构造
MAKE
类的一个实例。然后我们在该实例上调用
create<Type1>()
方法。但是,编译器在编译这个
create
方法的时候,还不知道
MAKE
是什么,所以不知道它的
create
方法是一个模板方法。所以烦人的是我们必须明确地告诉它这个方法是一个模板方法,导致
MAKE{}.template create<Type1>()
.

© www.soinside.com 2019 - 2024. All rights reserved.