如何在运行时习惯性地存储 unique_ptr 或 shared_ptr?

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

How to idiomatically store a unique_ptr or shared_ptr at runtime?

问题

以下是您要求的代码部分的中文翻译:

// 我有一个 `Foo` 类的实例,将传递一个智能指针给一个依赖对象。这个智能指针可以是 `unique_ptr`,如果调用者想要将对象的所有权传递给 `Foo` 实例,或者是 `shared_ptr`,如果调用者想要与 `Foo` 实例和其他内容共享对象。也许有一天它甚至可以接受 `weak_ptr`,以便在对象存在的时间内使用共享对象(但让我们忽略 `weak_ptr`,它显然会使事情变得复杂)。

我的问题是,管理这种通用智能指针的惯用方法是什么?

一个浮现的想法是使用 `shared_ptr` 进行存储,并使用重载的函数来加载它:

```cpp
#include <iostream>
#include <memory>

class Bar {};

class Foo {
public:
    void store(std::unique_ptr<Bar> p) { p_ = std::move(p); }
    void store(std::shared_ptr<Bar> p) { p_ = std::move(p); }
    // void store(std::weak_ptr<Bar> p) { p_ = p.lock(); }   // 不要担心 weak_ptr
private:
    std::shared_ptr<Bar> p_;  // shared_ptr 可以存储 unique_ptr,但不可逆转
};

int main() {
    Foo f {};

    // 将 ub 的所有权传递给 f
    auto ub = std::make_unique<Bar>();
    f.store(std::move(ub));

    // 创建 sb 的共享所有权,与 f 共享
    auto sb = std::make_shared<Bar>();
    f.store(sb);
}

我想另一种选择可能是使用一个模板化的类,在编译时确定存储类型,但在我的情况下,我需要 Foo 实例在运行时潜在地接受任何两种指针之一。而且,一旦将指针存储为 shared_ptr,就无法将其返回为 unique_ptr

是否有更好的方法来处理两个重载和转换为存储的 shared_ptr 之外的方法?


<details>
<summary>英文:</summary>

I have an instance of class `Foo` that will be passed a smart pointer to a dependency object. This may be a `unique_ptr`, if the caller wants to transfer ownership of the object to the `Foo` instance, or a `shared_ptr` if the caller wants to share the object with the `Foo` instance and other things. Perhaps one day it might even accept a `weak_ptr` so that it can make use of a shared object, for as long as it exists (EDIT: but let&#39;s ignore `weak_ptr`, it clearly complicates things).

My question is, what is the idiomatic way to manage a generic smart pointer in this way?

One idea that comes to mind is to use a `shared_ptr` for storage, and overloaded functions to load it:

#include <iostream>
#include <memory>

class Bar {};

class Foo {
public:
void store(std::unique_ptr<Bar> p) { p_ = std::move(p); }
void store(std::shared_ptr<Bar> p) { p_ = std::move(p); }
// void store(std::weak_ptr<Bar> p) { p_ = p.lock(); } // don't worry about weak_ptr
private:
std::shared_ptr<Bar> p_; // a shared_ptr can store a unique_ptr, but irreversibly
};

int main() {
Foo f {};

// pass ownership of ub to f
auto ub = std::make_unique&lt;Bar&gt;();
f.store(std::move(ub));

// create shared ownership of sb, share with f
auto sb = std::make_shared&lt;Bar&gt;();
f.store(sb);

}

I suppose an alternative could be to use a templated class, to determine the storage type at compile time, but in my case I need the `Foo` instance to potentially accept any of the two pointers at run-time. Also, once the pointer is stored as a `shared_ptr`, it is not possible to return it to a `unique_ptr`.

Is there a better approach than two overloads and conversion to a stored `shared_ptr`?

----

I&#39;m going to attempt to rephrase my question below, to make it clearer what I&#39;m trying to do. Unfortunately this part is now a &quot;design question&quot; and therefore can&#39;t be asked in its own question on SO.

I have a script-driven system that creates properties of a hierarchy of `Object`s - think 3D objects with associated materials. As the script is parsed, the object hierarchy (tree) is built, and materials are created and associated with these objects. Some materials are solely owned by a single Object, some are shared between multiple objects, and some may be weak references to other materials (if it helps, drop this requirement, I&#39;m more concerned about unique/shared ownership).

The materials are created with a factory function, that currently returns a `unique_ptr&lt;Material&gt;`. I chose this because Scott Meyers suggests that it is the most versatile type to return from a factory function, because it doesn&#39;t require the factory function to know about any final ownership strategy. The caller of the factory function can change this `unique_ptr` into anything else, like a `shared_ptr` or even a `weak_ptr`, depending on how it intends ownership of this Material to be handled.

Then this is passed to an Object, with the caller determining how the Object will manage this material. Perhaps it will be given as sole owner, or perhaps it will be shared between multiple Objects. So an Object needs to be able to accept, and store, either a `unique_ptr` or `shared_ptr`. Once it has this pointer, it doesn&#39;t actually care what it is, it&#39;s really just for Material clean-up once the Object is destroyed.

Comments suggest that this is an unusual pattern - and if so, I&#39;m keen to hear about alternative designs that might accomplish the same thing - which I could imagine could be described as &quot;lifetime management of heap-allocated factory-created dependencies that are injected into another object&quot;.

</details>


# 答案1
**得分**: 1

使用 `shared_por` 作为主要情况,当传递一个 `unique_ptr` 时,获取原始指针并从中创建一个 `shared_ptr`。要么这很简单,要么我漏掉了某些东西。

<details>
<summary>英文:</summary>

Use shared_por as the main case and when passed a unique_ptr, take the raw pointer and create a shared_ptr from it. Either this is simple or I’m missing something 

</details>



# 答案2
**得分**: 0

I'd write a wrapper for this that can deal with any of those pointer types. Note that in your example, the initialization of the weak pointer is incorrect, as it will grab onto the resource and prevent it from being destroyed. That does not feel like predictable behavior.

Here's an implementation that can handle all these pointer types, making them just behave as "a pointer".

```cpp
template <typename T>
class Pointer {
  private:
    std::variant<std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
    static std::shared_ptr<T> get(std::weak_ptr<T> const& p) {
      // consider throwing if p.expired()
      return p.lock();
    }
    static auto const& get(auto const& p) {
      return p;
    }
  public:
    Pointer(std::unique_ptr<T> p) : ptr{std::move(p)} {}
    Pointer(std::shared_ptr<T> p) : ptr{std::move(p)} {}
    Pointer(std::weak_ptr<T> p) : ptr{std::move(p)} {}
    Pointer(Pointer const&) = delete;
    Pointer(Pointer&&) = default;
    Pointer& operator=(Pointer const&) = delete;
    Pointer& operator=(Pointer&&) = default;
    ~Pointer() = default;

    auto& operator*() const {
      return std::visit([](auto const& sp) -> T& { return get(sp).operator*(); }, ptr);
    }
    auto operator->() const {
      return std::visit([](auto const& sp) { return get(sp).operator->(); }, ptr);
    }
};

This is how you would use it:

Pointer a{std::make_unique<int>(1)};
assert(*a == 1);
Pointer b{std::make_shared<int>(2)};
assert(*b == 2);
{
  auto const s = std::make_shared<int>(3);
  Pointer c{std::weak_ptr(s)};
  assert(*c == 3);
}
Pointer d{std::weak_ptr(std::make_shared<int>(4))};
assert(*d == 4); // UB, but could be made to throw (see above)

Check out the live demo on compiler explorer.

英文:

I'd write a wrapper for this that can deal with any of those pointer types. Note that in your example, the initialisation of the weak pointer is incorrect, as it will grab onto the resource and prevent it from being destroyed. That does not feel like predictable behaviour.

Here's an implementation that can handle all these pointer types, making them just behave as "a pointer".

template &lt;typename T&gt;
class Pointer {
  private:
    std::variant&lt;std::unique_ptr&lt;T&gt;,std::shared_ptr&lt;T&gt;,std::weak_ptr&lt;T&gt;&gt; ptr;
    static std::shared_ptr&lt;T&gt; get(std::weak_ptr&lt;T&gt; const&amp; p) {
      // consider throwing if p.expired()
      return p.lock();
    }
    static auto const&amp; get(auto const&amp; p) {
      return p;
    }
  public:
    Pointer(std::unique_ptr&lt;T&gt; p) : ptr{std::move(p)} {}
    Pointer(std::shared_ptr&lt;T&gt; p) : ptr{std::move(p)} {}
    Pointer(std::weak_ptr&lt;T&gt; p) : ptr{std::move(p)} {}
    Pointer(Pointer const&amp;) = delete;
    Pointer(Pointer&amp;&amp;) = default;
    Pointer&amp; operator=(Pointer const&amp;) = delete;
    Pointer&amp; operator=(Pointer&amp;&amp;) = default;
    ~Pointer() = default;

    auto&amp; operator*() const {
      return std::visit([](auto const&amp; sp) -&gt; T&amp; { return get(sp).operator*();}, ptr);
    }
    auto operator-&gt;() const {
      return std::visit([](auto const&amp; sp) { return get(sp).operator-&gt;();}, ptr);
    }
};

This is how you would use it:

  Pointer a{std::make_unique&lt;int&gt;(1)};
  assert(*a == 1);
  Pointer b{std::make_shared&lt;int&gt;(2)};
  assert(*b == 2);
  {
    auto const s = std::make_shared&lt;int&gt;(3);
    Pointer c{std::weak_ptr(s)};
    assert(*c == 3);
  }
  Pointer d{std::weak_ptr(std::make_shared&lt;int&gt;(4))};
  assert(*d == 4); // UB, but could be made to throw (see above)

Check out the live demo on compiler explorer.

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

发表评论

匿名网友

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

确定