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

fnvucqvd  于 2023-04-08  发布在  其他
关注(0)|答案(1)|浏览(106)

我有一个类Foo的示例,它将被传递一个指向依赖对象的智能指针。这可能是一个unique_ptr,如果调用者想要将对象的所有权转移到Foo示例,或者如果调用者想要与Foo示例和其他东西共享对象,则使用shared_ptr。也许有一天它甚至会接受一个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更好的方法?
我将尝试在下面重新表述我的问题,以使我想做的事情更清楚。不幸的是,这部分现在是一个“设计问题”,因此不能在SO的问题中提出。
我有一个脚本驱动的系统,它创建Object s层次结构的属性-想想3D对象与相关材料。(树),创建材质并与这些对象关联。有些材质由单个对象单独拥有,有些在多个对象之间共享,有些可能是对其他材质的弱引用(如果有帮助,放弃这个要求,我更关心的是唯一/共享所有权)。
这些材料是用工厂函数创建的,它当前返回一个unique_ptr<Material>。我选择这个是因为Scott Meyers认为它是从工厂函数返回的最通用的类型,因为它不需要工厂函数知道任何最终的所有权策略。工厂函数的调用者可以将这个unique_ptr更改为任何其他类型。例如shared_ptr甚至weak_ptr,这取决于它打算如何处理此材料的所有权。
然后,它被传递给一个Object,由调用者决定该Object将如何管理此材质。也许它将作为唯一的所有者,也许它将在多个Object之间共享。因此,Object需要能够接受并存储unique_ptrshared_ptr。一旦它有了这个指针,它实际上并不关心它是什么。它实际上只是为了在对象被销毁后进行材质清理。
评论表明这是一种不寻常的模式--如果是这样,我很想听到可能实现同样事情的替代设计--我可以想象它可以被描述为“注入到另一个对象中的堆分配工厂创建的依赖关系的生命周期管理”。

qq24tv8q

qq24tv8q1#

我会为此写一个 Package 器,可以处理任何这些指针类型。注意,在你的例子中,弱指针的初始化是不正确的,因为它会抓住资源并防止它被破坏。这感觉不像是可预测的行为。
这里有一个可以处理所有这些指针类型的实现,使它们只是表现为“指针”。

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

相关问题