本文介绍了在不使用弱指针的情况下解决智能指针循环引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有一个设计,其中一个对象集合可能对该集合中的其他对象具有往复依赖关系:

Suppose we have a design where a collection of objects have a possibly reciprocating dependency on other objects within the collection:

struct Object
{
  ...
  virtual void method();
private:
  std::vector<std::shared_ptr<Object>> siblings;
};

允许出现循环引用(并不代表退化的情况)。通常,循环引用将通过弱指针来解决,但这需要所有权的层次结构概念,不适用于所有对象都是同等对象的情况。

Circular references are allowed to occur (and do not represent degenerate cases). Normally, circular references would be resolved by weak pointers, but this requires a hierarchical concept of ownership, which doesn't apply in the scenario where all objects are equal peers.

我们如何解决不使用弱指针的循环引用问题?是否为此提供设计模式和/或是否可以应用专门的垃圾收集库? (专用是指不是保守的垃圾收集器,它会扫描整个内存空间中的根,例如Boehm GC,而是提供一种API,将操作范围限制为仅关注对象,并提供

How do we fix the problem of circular references without using weak pointers? Is there a design pattern for this and/or are there specialised garbage collection libraries that could be applied? ("Specialised" in the sense of not being conservative garbage collectors that scan the entire memory space for roots, such as the Boehm GC, but rather, provide an API that limits the scope of operation to just the objects of interest and provides the means of explicitly annotating/enumerating roots in the managed objects.)

当然,我认为理想的解决方案是避免发生相互依赖的设计,但是出于当前问题的目的,请使用无法避免相互依赖设计的约束条件。通过激励示例,考虑一个递归神经网络,其中每个神经元都表示为一个对象,该对象显式存储对其连接的神经元的引用。

Of course, I respect that the ideal solution is to avoid designs where mutual dependencies occur, but for the purposes of the current question, please work with the constraint that a mutual dependency design cannot be avoided. By way of motivating example, consider a recurrent neural network where each neuron is represented as an object that explicitly stores references to its connected neurons.

我已将问题标记为 C ++ ,但也欢迎使用与语言无关的答案。

I've tagged the question C++, but language-agnostic answers are also welcomed.

推荐答案

在某些情况下,我们可以将 Object 实例作为一个群组进行管理, std :: shared_ptr 的别名构造函数提供了部分解决方案。我认为这不是一个合适的解决方案,但我发布了它,希望引起更多讨论。我将不使用完全通用的公式化,而是在人工神经网络的拟议用例上下文中描述该解决方案。

In certain circumstances where we can manage Object instances as a group, the aliasing constructor of std::shared_ptr provides a partial solution to the problem. I don't consider this a proper solution, but I'm posting it in hope of spurring further discussion. Instead of using a completely generic formularisation, I'll describe the solution in the context of the proposed use-case of an artificial neural network.

我们有一个 Neuron 类,其中每个实例都以可能的往复关系引用其他神经元(即,预期会出现循环引用)。

We have a Neuron class where each instance references other neurons in a possibly reciprocating relationship (i.e., circular references are expected to occur).

struct Neuron {
  std::shared_ptr<Neuron> inputs, outputs;
};

我们希望对 Neuron 实例进行自动内存管理,以便只要我们拥有指向 Neuron 的智能指针,我们就可以确保其所有依赖项都保持活动状态(即未过期)。

We want automatic memory management of Neuron instances so that for as long as we hold a smart pointer to a Neuron, we can be sure that all its dependencies remain active (i.e., not expired).

将神经元分组为网络是很自然的,因此我们可以引入 Network 类,该类是一个管理并拥有 Neuron 实例的集合:

It's natural to group neurons into networks, and so we can introduce a Network class, which is a container that manages and owns a collection of Neuron instances:

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 的所有依赖项仍应保持活动状态:

The API allows the client to get individual Neuron instances as shared pointers. While the client holds such a pointer, it should not matter if the Network itself has gone out of scope; all dependencies of the referenced Neuron should still remain active:

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


分析


以上解决方案为我们提供了以下解决方案:

Analysis

The above solution gives us the following:


  • 客户端对单独持有的 Neuron获得正常的 std :: shared_ptr 语义/ code>实例。

  • 客户端无需担心 Network 容器超出范围时会发生什么情况。

  • Neuron 实例中的循环引用是允许的,并且不会干扰内存管理。

  • Clients get normal std::shared_ptr semantics for individually held Neuron instances.
  • Clients do not need to worry about what happens when their Network container goes out of scope.
  • Circular references within Neuron instances are allowed and do not interfere with memory management.

但是,它具有以下局限性,因此充其量只能作为部分解决方案,甚至根本不是解决方案:

However, it has the following limitations, which makes it at best a partial solution, and possibly not a solution at all:


  • 要求管理必须由具有所有权语义的某些容器类执行。

  • 不支持多个容器共同拥有对象(即 Neuron 只能属于一个网络)。

  • Requires management to be performed by some container class with ownership semantics.
  • Does not support multiple containers co-owning objects (i.e., a Neuron can only belong to a single Network).

仍然非常在寻找更好的答案,但与此同时,希望这可能是好奇一些逝去的灵魂。

Still very much looking for better answers, but in the mean time, hope this might be of curiosity to some passing soul.

这篇关于在不使用弱指针的情况下解决智能指针循环引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-25 09:47