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

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

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:

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

Where the former would do something like:

  1. switch (id)
  2. {
  3. case kID1:
  4. return std::make_unique&lt;Type1&gt;();
  5. }

And the latter

  1. switch (id)
  2. {
  3. case kID1:
  4. return std::make_shared&lt;Type1&gt;();
  5. }

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:

  1. template &lt;typename PTR, typename MAKE&gt;
  2. PTR create(IDType id)
  3. {
  4. switch (id)
  5. {
  6. case kID1:
  7. return MAKE&lt;Type1&gt;();
  8. }
  9. }

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来调用该函数,比如:

  1. auto my_ptr = createInterface(id);

这样,my_ptr 将是一个 unique_ptr 或者

  1. 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

  1. auto my_ptr = createInterface(id);

which has my_ptr being a unique_ptr or

  1. 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:

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

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

  1. class AbstractType {
  2. public:
  3. virtual ~AbstractType() = default;
  4. virtual int f() const = 0;
  5. };
  6. class Foo : public AbstractType {
  7. public:
  8. int f() const override
  9. {
  10. return 1;
  11. }
  12. };
  13. class Bar : public AbstractType {
  14. public:
  15. int f() const override
  16. {
  17. return 2;
  18. }
  19. };
  20. enum class IDType {
  21. Foo,
  22. Bar,
  23. };
  24. class Factory {
  25. public:
  26. std::unique_ptr&lt;AbstractType&gt; createUnique(IDType id);
  27. std::shared_ptr&lt;AbstractType&gt; createShared(IDType id);
  28. private:
  29. template &lt;typename MakePtr&gt;
  30. auto createUniversal(IDType id);
  31. };
  32. //======================
  33. class WrapMakeUnique {
  34. public:
  35. template &lt;typename Base, typename T, typename... Args&gt;
  36. static auto make(Args... args) -&gt; std::unique_ptr&lt;Base&gt;
  37. {
  38. return std::make_unique&lt;T&gt;(std::forward&lt;Args&gt;(args)...);
  39. }
  40. };
  41. class WrapMakeShared {
  42. public:
  43. template &lt;typename Base, typename T, typename... Args&gt;
  44. static auto make(Args... args) -&gt; std::shared_ptr&lt;Base&gt;
  45. {
  46. return std::make_shared&lt;T&gt;(std::forward&lt;Args&gt;(args)...);
  47. }
  48. };
  49. template &lt;typename MakePtr&gt;
  50. auto Factory::createUniversal(IDType id)
  51. {
  52. switch (id) {
  53. case IDType::Foo:
  54. return MakePtr::template make&lt;AbstractType, Foo&gt;();
  55. case IDType::Bar:
  56. return MakePtr::template make&lt;AbstractType, Bar&gt;();
  57. }
  58. throw std::invalid_argument { &quot;IDType out of range&quot; };
  59. }
  60. std::unique_ptr&lt;AbstractType&gt; Factory::createUnique(IDType id)
  61. {
  62. return createUniversal&lt;WrapMakeUnique&gt;(id);
  63. }
  64. std::shared_ptr&lt;AbstractType&gt; Factory::createShared(IDType id)
  65. {
  66. return createUniversal&lt;WrapMakeShared&gt;(id);
  67. }

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

答案3

得分: 1

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

  1. template <typename PTR, typename MAKE>
  2. PTR create(IDType id)
  3. {
  4. switch (id)
  5. {
  6. case kID1:
  7. return MAKE<Type1>();
  8. }
  9. }

你所错过的关键部分似乎是 (1) MAKE 是一个类型,所以 MAKE<Type1>() 是我们可能需要跳过的语法,以及 (B) 如何使用它。

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

  1. template<class Base>
  2. struct make_unique_factory {
  3. using PTR = std::unique_ptr<Base>;
  4. template<class Derived, class... Args>
  5. PTR create(Args&&... args) const
  6. {return std::make_unique<Derived>(std::forward<Args>(args)...);}
  7. };
  8. template<class Base>
  9. struct make_shared_factory {
  10. using PTR = std::shared_ptr<Base>;
  11. template<class Derived, class... Args>
  12. PTR create(Args&&... args) const
  13. {return std::make_shared<Derived>(std::forward<Args>(args)...);}
  14. };

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

然后使用方式与你之前的类似:

  1. template <typename MAKE>
  2. auto create(IDType id) -> typename MAKE::PTR
  3. {
  4. switch (id)
  5. {
  6. case kID1:
  7. return MAKE{}.template create<Type1>();
  8. }
  9. }
  10. std::unique_ptr<AbstractType> createUnique(IDType id) {
  11. return create<make_unique_factory<AbstractType>>(id);
  12. }
  13. std::shared_ptr<AbstractType> createShared(IDType id) {
  14. return create<make_shared_factory<AbstractType>>(id);
  15. }

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

MAKE{}.template create<Type1>()

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

英文:

Your generic factory method is very close:

  1. template &lt;typename PTR, typename MAKE&gt;
  2. PTR create(IDType id)
  3. {
  4. switch (id)
  5. {
  6. case kID1:
  7. return MAKE&lt;Type1&gt;();
  8. }
  9. }

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:

  1. template&lt;class Base&gt;
  2. struct make_unique_factory {
  3. using PTR = std::unique_ptr&lt;Base&gt;;
  4. template&lt;class Derived, class... Args&gt;
  5. PTR create(Args&amp;&amp;... args) const
  6. {return std::make_unique&lt;Derived&gt;(std::forward&lt;Args&gt;(args)...);}
  7. };
  8. template&lt;class Base&gt;
  9. struct make_shared_factory {
  10. using PTR = std::shared_ptr&lt;Base&gt;;
  11. template&lt;class Derived, class... Args&gt;
  12. PTR create(Args&amp;&amp;... args) const
  13. {return std::make_shared&lt;Derived&gt;(std::forward&lt;Args&gt;(args)...);}
  14. };

(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:

  1. template &lt;typename MAKE&gt;
  2. auto create(IDType id) -&gt; typename MAKE::PTR
  3. {
  4. switch (id)
  5. {
  6. case kID1:
  7. return MAKE{}.template create&lt;Type1&gt;();
  8. }
  9. }
  10. std::unique_ptr&lt;AbstractType&gt; createUnique(IDType id) {
  11. return create&lt;make_unique_factory&lt;AbstractType&gt;&gt;(id);
  12. }
  13. std::shared_ptr&lt;AbstractType&gt; createShared(IDType id) {
  14. return create&lt;make_shared_factory&lt;AbstractType&gt;&gt;(id);
  15. }

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:

确定