如何解决在不使用弱指针的情况下解决智能指针循环引用
假设我们有一个设计,其中一个对象集合可能对该集合中的其他对象具有往复依赖性:
struct Object
{
...
virtual void method();
private:
std::vector<std::shared_ptr<Object>> siblings;
};
允许出现循环引用(不代表退化的情况)。通常,循环引用可以通过弱指针来解决,但这需要所有权的层次结构概念,不适用于所有对象都是同等对象的情况。
如何在不使用弱指针的情况下解决循环引用的问题?是否为此提供设计模式和/或是否可以应用专门的垃圾收集库? (“专用”的含义不是保守的垃圾收集器,它会扫描整个内存空间中的根,例如Boehm GC,而是提供一种API,该API将操作范围限制为仅关注的对象,并提供了显式注释/枚举受管对象中的根。)
当然,我认为理想的解决方案是避免发生相互依赖的设计,但是出于当前问题的目的,请使用不能避免相互依赖设计的约束。通过激励示例,考虑一个递归神经网络,其中每个神经元都表示为一个对象,该对象显式存储对其连接的神经元的引用。
我已标记问题C++
,但也欢迎与语言无关的答案。
解决方法
一种解决方案是让每种类型的成员释放所有引用。
struct type1
{
std::shared_ptr<struct type2> ptr;
void reset() { ptr.reset(); }
};
struct type2
{
std::shared_ptr<type1> ptr;
type2(std::shared_ptr<type1> & ptr) : ptr{ptr} {}
void reset() { ptr.reset(); }
};
它不能像适当的RAII一样自动进行(因为需要额外的步骤,而不仅是依赖于析构函数),但是只要您遵循合同,它就会使对象保持活动状态,只要它们需要然后将它们释放。根据确切的用法,可能还需要进行两步初始化(例如,使用type1对象,创建该对象,将其分配给shared_ptr,然后再创建type2对象)。
尽管这是您的模式,但您也经常可以移动到只让type2存储原始指针,而不必担心所有拥有对象的生命周期。使用这样的环形链 somewhere 时,必须有一个外部参考,这才是开始展开的合适位置。
,在某些情况下,我们可以将Object
实例作为一个组来管理,std::shared_ptr
的别名构造函数为该问题提供了部分解决方案。我认为这不是一个合适的解决方案,但我将其发布,希望引起更多讨论。我将在拟议的人工神经网络用例的背景下描述解决方案,而不是使用完全通用的公式化。
问题
我们有一个Neuron
类,其中每个实例都以可能的往复关系引用其他神经元(即,预期会出现循环引用)。
struct Neuron {
std::shared_ptr<Neuron> inputs,outputs;
};
我们希望自动管理Neuron
实例的内存,以便只要我们持有指向Neuron
的智能指针,就可以确保其所有依赖项都保持活动状态(即未过期)
部分解决方案
将神经元分组为网络是很自然的,因此我们可以引入Network
类,它是一个管理并拥有Neuron
实例集合的容器:
class Network : public std::enable_shared_from_this<Network> {
std::vector<Neuron> neurons;
public:
static std::shared_ptr<Network> createNetwork();
std::shared_ptr<Neuron> getNeuron(size_t indx);
};
API允许客户端获取单个Neuron
实例作为共享指针。当客户端拥有这样的指针时,Network
本身是否超出范围并不重要;引用的Neuron
的所有依赖项仍应保持活动状态:
std::shared_ptr<Neuron> neuron;
{
auto network = Network::createNetwork();
neuron = network.getNeuron(0);
}
neuron.inputs[0]; // <-- alive and well despite the
// {network} smart pointer
// having been destructed.
为此,我们可以使用std::shared_ptr
的别名构造函数:
std::shared_ptr<Neuron> Network::getNeuron(size_t const indx) {
return std::shared_ptr<Neuron>(shared_from_this(),&neurons[indx]);
}
分析
以上解决方案为我们提供了以下内容:
- 客户端对于单独持有的
std::shared_ptr
实例具有正常的Neuron
语义。 - 客户无需担心
Network
容器超出范围时会发生什么情况。 - 在
Neuron
实例中允许使用循环引用,并且不会干扰内存管理。
但是,它具有以下局限性,因此充其量只能作为部分解决方案,甚至根本不是解决方案:
- 要求管理必须由具有所有权语义的某些容器类执行。
- 不支持多个容器共同拥有对象(即
Neuron
只能属于单个Network
)。
仍在寻找更好的答案,但与此同时,希望这可能对某些过世的灵魂产生好奇。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。