如何编写适用于std::shared_ptr和std::unique_ptr的模板工厂函数。

huangapple go评论62阅读模式
英文:

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

问题

以下是翻译好的部分:

我经常编写类似以下签名的工厂:

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_sharedmake_unique)和返回类型(shared_ptrunique_ptr)。这导致了代码重复。

我想知道如何编写一个模板化的create函数,它接受指针类型和“make”函数类型,并使用它们。类似于以下内容:

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

我知道上面的代码是无效的。

此外,我知道可以从std::unique_ptr创建std::shared_ptr,反之亦然,但是这个工厂将在不同的应用程序中使用,其中一个可能使用shared_ptr,而另一个可能使用unique_ptr。这样,代码是可重复使用的,但也对特定用例高效。

英文:

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

std::unique_ptr&lt;AbstractType&gt; createUnique(IDType id);
std::shared_ptr&lt;AbstractType&gt; createShared(IDType id);

Where the former would do something like:

switch (id)
{
case kID1:
    return std::make_unique&lt;Type1&gt;();
}

And the latter

switch (id)
{
case kID1:
    return std::make_shared&lt;Type1&gt;();
}

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 &lt;typename PTR, typename MAKE&gt;
PTR create(IDType id)
{
    switch (id)
    {
    case kID1:
        return MAKE&lt;Type1&gt;();
    }
}

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.

答案1

得分: 6

只有一个返回类型是 std::unique_ptr&lt;AbstractType&gt;。 如果这样做,你可以使用std::unique_ptrstd::shared_ptr来调用该函数,比如:

auto my_ptr = createInterface(id);

这样,my_ptr 将是一个 unique_ptr 或者

std::shared_ptr&lt;AbstractType&gt; my_ptr = createInterface(id);

现在,返回的 unique_ptr 被转换为 shared_ptr

英文:

My advice, only have a single return type of std::unique_ptr&lt;AbstractType&gt;. 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&lt;AbstractType&gt; my_ptr = createInterface(id);

and now the returned unique_ptr is converted into a shared_ptr.

答案2

得分: 4

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

避免失去对 `std::make_shared` 的使用,该方法优化了内存使用,我会以以下方式解决这个问题:
英文:

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&lt;AbstractType&gt; createUnique(IDType id);
    std::shared_ptr&lt;AbstractType&gt; createShared(IDType id);

private:
    template &lt;typename MakePtr&gt;
    auto createUniversal(IDType id);
};

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

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

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

std::unique_ptr&lt;AbstractType&gt; Factory::createUnique(IDType id)
{
    return createUniversal&lt;WrapMakeUnique&gt;(id);
}

std::shared_ptr&lt;AbstractType&gt; Factory::createShared(IDType id)
{
    return createUniversal&lt;WrapMakeShared&gt;(id);
}

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

答案3

得分: 1

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

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>
    PTR 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>
    PTR 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);
}

这是你遇到问题的原因可能是这行代码:

MAKE{}.template create<Type1>()

所以我来解释一下。MAKE{} 构造了 MAKE 类的一个实例。然后,我们在该实例上调用 create<Type1>() 方法。但是,在编译器编译这个 create() 方法时,它还不知道 MAKE 是什么,所以不知道它的 create() 方法是一个模板方法。因此,令人讨厌的是,我们必须明确告诉编译器这个方法是一个模板方法,因此出现了 MAKE{}.template create<Type1>()

英文:

Your generic factory method is very close:

template &lt;typename PTR, typename MAKE&gt;
PTR create(IDType id)
{
    switch (id)
    {
    case kID1:
        return MAKE&lt;Type1&gt;();
    }
}

The key parts you're missing appear to be that (1) MAKE is a type, so MAKE&lt;Type1&gt;() is a syntax we'll probably want to skip, and (B) how to use it.

The ideal way to use it is with two factory classes:

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

(We use classes and not methods so that the compiler has an easier time inlining. Method parameters are harder to inline)

And then use is similar to what you had:

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

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

The reason you had trouble is probably this line:

MAKE{}.template create&lt;Type1&gt;()

So I'll break that down. MAKE{} constructs an instance of the MAKE class. Then, we call the create&lt;Type1&gt;() method on that instance. However, when the compiler is compiling this create() method, it doesn't yet know what MAKE is, so doesn't know that its create() method is a template method. So annoyingly, we have to tell it explicitly that this method is a template method, leading to MAKE{}.template create&lt;Type1&gt;().

huangapple
  • 本文由 发表于 2023年4月7日 00:17:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/75951654.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定