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



我有一个类Foo的实例,它将被传递一个指向依赖对象的智能指针。如果调用者想要将对象的所有权转移到Foo实例,这可能是unique_ptr;如果调用者想要与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上单独提问。

我有一个脚本驱动的系统,它创建了Objects的层次结构的属性-认为具有相关材料的3D对象。当脚本被解析时,对象层次结构(树)被构建,材料被创建并与这些对象相关联。有些材料由单个对象单独拥有,有些在多个对象之间共享,有些可能是对其他材料的弱引用(如果有帮助,请删除此要求,我更关心唯一/共享所有权)。

材料是用工厂函数创建的,目前返回一个unique_ptr<Material>。我之所以选择这种类型,是因为Scott Meyers认为这是从工厂函数返回的最通用的类型,因为它不需要工厂函数知道任何最终所有权策略。工厂函数的调用者可以将此unique_ptr更改为其他任何东西,例如shared_ptr甚至weak_ptr,这取决于它打算如何处理此材料的所有权。

然后将其传递给对象,由调用者决定对象将如何管理此材料。也许它将作为唯一的所有者,或者它将在多个对象之间共享。因此,对象需要能够接受和存储unique_ptrshared_ptr。一旦它有了这个指针,它实际上并不关心它是什么,它实际上只是用于对象销毁后的材料清理。

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

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

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

这是一个可以处理所有这些指针类型的实现,使它们仅仅表现为"指针"。

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)

查看编译器资源管理器的实时演示。

最新更新