在不使用弱指针的情况下解决智能指针循环引用

如何解决在不使用弱指针的情况下解决智能指针循环引用

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

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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-