当前位置: 首页 > news >正文

面试题:循环引用两个节点相互引用,如何判断哪个用 shared_ptr?哪个用 weak_ptr?

目录

1.引言

2.原理

3.所有权模型与指针选择

4.复杂场景的决策策略

5.注意事项

6.总结


1.引言

两个对象通过 shared_ptr 相互引用时,会产生循环引用问题,导致内存泄漏。因为这两个对象的引用计数永远不会变为 0,即使它们在程序的其他部分已经不被使用了。

典型循环引用:

#include <memory>
#include <iostream>
using namespace std;classB; // 前置声明classA {
public:shared_ptr<B> b_ptr;~A() { cout << "A destroyed" << endl; }
};classB {
public:shared_ptr<A> a_ptr;~B() { cout << "B destroyed" << endl; }
};voidtest(){shared_ptr<A> a = make_shared<A>();shared_ptr<B> b = make_shared<B>();a->b_ptr = b;b->a_ptr = a;
} 
// test结束时,a和b的引用计数均为1,对象未销毁

解决方案是打破这个循环,通常是让其中一个对象使用 weak_ptr 指向另一个对象,而另一个对象使用 shared_ptr。这里决定使用 shared_ptr 还是 weak_ptr 的关键在于所有权关系的分析

2.原理

  • std::shared_ptr 会对所管理的对象进行引用计数,每有一个 std::shared_ptr 指向该对象,引用计数就会加 1;当引用计数降为 0 时,对象会被自动销毁,影响引用次数的场景包括:构造、赋值、析构
  • std::weak_ptr 是一种弱引用,它不会增加所管理对象的引用计数,主要用于观察 std::shared_ptr 所管理的对象,避免循环引用。

3.所有权模型与指针选择

一般来说,应该在拥有对象所有权的地方使用 std::shared_ptr,在只需要观察对象、不拥有对象所有权的地方使用 std::weak_ptr。以下通过几个具体场景进行说明:

场景一:父节点和子节点的关系

#include <iostream>
#include <memory>class Child;class Parent {
public:std::shared_ptr<Child> child;~Parent() {std::cout << "Parent destroyed" << std::endl;}
};class Child {
public:std::weak_ptr<Parent> parent;~Child() {std::cout << "Child destroyed" << std::endl;}
};int main() {auto parent = std::make_shared<Parent>();parent->child = std::make_shared<Child>();parent->child->parent = parent;return 0;
}

在上述代码里,Parent 类使用 std::shared_ptr 拥有 Child 对象的所有权,而 Child 类使用 std::weak_ptr 观察 Parent 对象,这样就避免了循环引用。

场景二:观察者模式

在观察者模式中,主题对象通常拥有观察者对象的所有权,而观察者对象只是对主题对象进行观察。所以,主题对象使用 std::shared_ptr 指向观察者对象,观察者对象使用 std::weak_ptr 指向主题对象。

#include <iostream>
#include <memory>
#include <vector>class Observer;class Subject {
public:std::vector<std::shared_ptr<Observer>> observers;~Subject() {std::cout << "Subject destroyed" << std::endl;}
};class Observer {
public:std::weak_ptr<Subject> subject;~Observer() {std::cout << "Observer destroyed" << std::endl;}
};int main() {auto subject = std::make_shared<Subject>();auto observer = std::make_shared<Observer>();subject->observers.push_back(observer);observer->subject = subject;return 0;
}

在这个例子中,Subject 类使用 std::shared_ptr 拥有 Observer 对象的所有权,而 Observer 类使用 std::weak_ptr 观察 Subject 对象,防止了循环引用的产生。

4.复杂场景的决策策略

场景一:多所有者场景

若多个对象共享某资源,需由顶层管理者持有shared_ptr,其余使用weak_ptr。

示例:缓存系统设计

#include <unordered_map>class CacheManager;class Resource {
public:weak_ptr<CacheManager> manager;  // 弱引用管理器
};class CacheManager : public enable_shared_from_this<CacheManager> {
public:void addResource(int id){resources[id] = make_shared<Resource>();resources[id]->manager = shared_from_this();  // 关键行}
};

说明:CacheManager拥有所有Resource,Resource通过weak_ptr反向引用管理器。

场景二:无明确所有权场景

若对象间无明确从属关系,需重新审视设计或使用双向weak_ptr。

示例:聊天室和用户

#include <memory>
#include <vector>
#include <iostream>
usingnamespace std;class ChatRoom;class User {
public:string name;vector<weak_ptr<ChatRoom>> rooms;  // 弱引用聊天室
};class ChatRoom {
public:string name;vector<weak_ptr<User>> users;  // 弱引用用户
};intmain(){auto alice = make_shared<User>("Alice");auto general = make_shared<ChatRoom>("General");return 0;
}

说明:用户(User) 可以加入多个聊天室,聊天室(ChatRoom) 包含多个用户,但是他们互相并没有所有权关系,所以使用双向weak_ptr,用户和聊天室的生命周期由外部系统管理!

5.注意事项

1)weak_ptr 的安全访问

使用weak_ptr时需通过lock()获取shared_ptr,并检查有效性。

void accessParent(shared_ptr<Child> child) {if (auto parent = child->parent.lock()) {cout << "Parent is alive: " << parent << endl;} else {cout << "Parent has been destroyed" << endl;}
}

2)循环引用的检测工具

Valgrind、AddressSanitizer 等工具可辅助检测内存泄漏。

静态代码分析器(如 Clang-Tidy)可识别潜在循环引用。

6.总结

核心准则:通过分析对象生命周期控制权,确定shared_ptr和weak_ptr的使用。始终确保至少有一条所有权路径不形成闭环。

谁管理生命周期,谁用 shared_ptr。

谁仅需引用对方,谁用 weak_ptr。

相关文章:

  • Pytorch实战
  • 软件架构师的“天、人、术、势“:构建未来系统的哲学框架
  • Linux 下依赖库的问题
  • OV-Watch(一)(IAP_F411学习)
  • 【Part 2安卓原生360°VR播放器开发实战】第一节|通过传感器实现VR的3DOF效果
  • Milvus(1):什么是 Milvus
  • 21. git apply
  • 大模型技术解析与应用 | 大语言模型:从理论到实践(第2版)| 复旦大学 | 533页
  • 深度学习方向急出成果,是先广泛调研还是边做实验边优化?
  • springboot自动装配的原理
  • 修改PointLIO项目
  • RHCSA知识点
  • 2025-4-19 情绪周期视角复盘(mini)
  • Linux命令--将控制台的输入写入文件
  • C语言之高校学生信息快速查询系统的实现
  • RocketMQ实现基于可靠消息的最终一致性
  • electron打包是没有正确生成electron.exe,x ENOENT: no such file or directory, rename:
  • 位运算---总结
  • 微信小程序上传腾讯云
  • Dubbo QoS操作手册
  • 国安部:机关工作人员用软件扫描涉密文件备份网盘致重大泄密
  • 大国重器飞天背后,有一位上海航天的“老法师”
  • 官方披露:定西民政局原局长将收受烟酒高价“倒卖”给单位,用于违规接待
  • 龚桢梽任广东省发展和改革委员会副主任
  • 河北衡水中学再换校长
  • 湖南省委书记人民日报撰文:坚定不移贯彻总体国家安全观,更好统筹高质量发展和高水平安全