如何在运行时以惯用方式存储 unique_ptr 或 shared_ptr?

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

我有一个类

Foo
的实例,它将传递一个指向依赖对象的智能指针。这可能是一个
unique_ptr
,如果调用者想要将对象的所有权转移给
Foo
实例,或者如果调用者想要与
shared_ptr
实例和其他东西共享对象,则可能是一个
Foo
。也许有一天它甚至可以接受一个
weak_ptr
,这样它就可以使用一个共享对象,只要它存在(编辑:但让我们忽略
weak_ptr
,它显然会使事情复杂化)。

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

想到的一个想法是用一个

shared_ptr
来存储,重载函数来加载它:

#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<Bar>();
    f.store(std::move(ub));

    // create shared ownership of sb, share with f
    auto sb = std::make_shared<Bar>();
    f.store(sb);
}

我想另一种方法是使用模板化类,在编译时确定存储类型,但在我的例子中,我需要

Foo
实例在运行时可能接受两个指针中的任何一个。此外,一旦指针存储为
shared_ptr
,就不可能将其返回到
unique_ptr

有没有比两次重载和转换为存储的更好的方法

shared_ptr


我将尝试在下面重新表述我的问题,以便更清楚地说明我正在尝试做什么。不幸的是,这部分现在是一个“设计问题”,因此不能在它自己的问题中提出。

我有一个脚本驱动的系统,可以创建

Object
s 的层次结构的属性 - 考虑具有相关材料的 3D 对象。随着脚本的解析,对象层次结构(树)被构建,材料被创建并与这些对象相关联。有些材料仅由单个对象拥有,有些在多个对象之间共享,有些可能是对其他材料的弱引用(如果有帮助,请放弃此要求,我更关心唯一/共享所有权)。

材质是使用工厂函数创建的,当前返回一个

unique_ptr<Material>
。我之所以选择它,是因为 Scott Meyers 建议它是从工厂函数返回的最通用的类型,因为它不需要工厂函数了解任何最终所有权策略。工厂函数的调用者可以将此
unique_ptr
更改为其他任何内容,如
shared_ptr
甚至
weak_ptr
,具体取决于它打算如何处理此材料的所有权。

然后将其传递给对象,调用者确定对象将如何管理此材料。也许它会作为唯一所有者提供,或者它可能会在多个对象之间共享。因此,对象需要能够接受和存储

unique_ptr
shared_ptr
。一旦有了这个指针,它实际上并不关心它是什么,它实际上只是在对象被销毁后进行材料清理。

评论表明这是一个不寻常的模式——如果是这样,我很想听听可能完成同样事情的替代设计——我可以想象它可以被描述为“堆分配的工厂创建的依赖项的生命周期管理被注入到另一个对象中。

c++ shared-ptr smart-pointers unique-ptr
1个回答
0
投票

我会为此编写一个可以处理任何这些指针类型的包装器。请注意,在您的示例中,弱指针的初始化是不正确的,因为它会抓住资源并防止其被破坏。这感觉不像是可预测的行为。

这是一个可以处理所有这些指针类型的实现,使它们的行为就像“指针”一样。

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

这就是你将如何使用它:

  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)

查看 live demo on compiler explorer.

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