我有一个类
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
。一旦有了这个指针,它实际上并不关心它是什么,它实际上只是在对象被销毁后进行材料清理。
评论表明这是一个不寻常的模式——如果是这样,我很想听听可能完成同样事情的替代设计——我可以想象它可以被描述为“堆分配的工厂创建的依赖项的生命周期管理被注入到另一个对象中。
我会为此编写一个可以处理任何这些指针类型的包装器。请注意,在您的示例中,弱指针的初始化是不正确的,因为它会抓住资源并防止其被破坏。这感觉不像是可预测的行为。
这是一个可以处理所有这些指针类型的实现,使它们的行为就像“指针”一样。
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.