shared_ptr 与 weak_ptr
定义与作用
std::shared_ptr(C++11)实现共享所有权——多个 shared_ptr 可指向同一对象,当最后一个 shared_ptr 被销毁时对象才被释放。底层通过引用计数管理生命周期。
std::weak_ptr(C++11)是"弱引用"——不增加引用计数,用于观察 shared_ptr 管理的对象和打破循环引用。
auto sp1 = std::make_shared<int>(42); // 引用计数 = 1
auto sp2 = sp1; // 引用计数 = 2
std::weak_ptr<int> wp = sp1; // 引用计数仍为 2
sp2.reset(); // 引用计数 = 1
核心原理
shared_ptr 的控制块
weak_ptr 打破循环引用
完整示例
示例一:企业组织架构中的 shared_ptr
场景说明:企业部门管理——多个部门共享一份公司资源,weak_ptr 用于观察者模式。
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>
// 公司资源——被多个部门共享
class SharedResource {
public:
explicit SharedResource(std::string name)
: name_(std::move(name)) {
std::cout << " 创建资源: " << name_ << std::endl;
}
~SharedResource() {
std::cout << " 销毁资源: " << name_ << std::endl;
}
const std::string& name() const { return name_; }
private:
std::string name_;
};
// 部门——持有 shared_ptr 共享公司资源
class Department {
public:
Department(std::string name, std::shared_ptr<SharedResource> res)
: name_(std::move(name)), resource_(std::move(res)) {}
void describe() const {
std::cout << " " << name_ << " 使用资源: "
<< resource_->name()
<< " (引用计数: " << resource_.use_count() << ")"
<< std::endl;
}
std::shared_ptr<SharedResource> resource() const { return resource_; }
const std::string& name() const { return name_; }
private:
std::string name_;
std::shared_ptr<SharedResource> resource_;
};
// 资源监视器——持有 weak_ptr 观察资源状态
class ResourceMonitor {
public:
void watch(std::shared_ptr<SharedResource> res) {
watched_.push_back(res);
}
void report() const {
std::cout << "===== 资源监视报告 =====" << std::endl;
for (const auto& wp : watched_) {
if (auto sp = wp.lock()) {
std::cout << " 活跃: " << sp->name()
<< " (引用计数: " << sp.use_count() << ")"
<< std::endl;
} else {
std::cout << " 已释放的资源" << std::endl;
}
}
}
private:
std::vector<std::weak_ptr<SharedResource>> watched_;
};
int main() {
std::cout << "===== shared_ptr 共享资源 =====" << std::endl;
// 创建一份共享资源
auto database = std::make_shared<SharedResource>("企业数据库");
std::cout << "初始引用计数: " << database.use_count() << std::endl;
// 多个部门共享同一资源
Department hr("人力资源部", database);
Department it("信息技术部", database);
Department finance("财务部", database);
std::cout << "\n各部门持有后引用计数: " << database.use_count() << std::endl;
hr.describe();
it.describe();
finance.describe();
// 监视器观察资源
ResourceMonitor monitor;
monitor.watch(database);
monitor.report();
std::cout << "\n--- 释放主引用 ---" << std::endl;
database.reset(); // main 放弃引用
std::cout << "主引用释放后:" << std::endl;
hr.describe();
std::cout << "\n--- 所有部门释放 ---" << std::endl;
// hr, it, finance 将在 main 结束时析构
// 离开作用域 → 3 个 Department 析构 → 所有 shared_ptr 销毁 → 资源释放
return 0;
}
预期输出:
===== shared_ptr 共享资源 =====
创建资源: 企业数据库
初始引用计数: 1
各部门持有后引用计数: 4
人力资源部 使用资源: 企业数据库 (引用计数: 4)
信息技术部 使用资源: 企业数据库 (引用计数: 4)
财务部 使用资源: 企业数据库 (引用计数: 4)
===== 资源监视报告 =====
活跃: 企业数据库 (引用计数: 4)
--- 释放主引用 ---
主引用释放后:
人力资源部 使用资源: 企业数据库 (引用计数: 3)
--- 所有部门释放 ---
销毁资源: 企业数据库
逐段分析:
std::make_shared<T>()一次分配对象 + 控制块——比shared_ptr<T>(new T)更高效use_count()返回当前强引用计数——调试友好- 多个 Department 持有同一份 shared_ptr——每个拷贝增加引用计数
- ResourceMonitor 用
weak_ptr观察——不增加引用计数,不影响资源生命周期 wp.lock()尝试提升为 shared_ptr——如果资源已释放返回空
示例二:双向关系中 weak_ptr 打破循环引用
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Person; // 前向声明
// 使用 weak_ptr 避免循环引用
class Person {
public:
Person(std::string name) : name_(std::move(name)) {
std::cout << " 构造: " << name_ << std::endl;
}
~Person() {
std::cout << " 析构: " << name_ << std::endl;
}
// 设置配偶(双向关联)
void setSpouse(std::shared_ptr<Person> spouse) {
spouse_ = spouse; // weak_ptr——不增加引用计数
}
// 添加朋友
void addFriend(std::shared_ptr<Person> friend_) {
friends_.push_back(friend_);
}
void describe() const {
std::cout << " " << name_;
if (auto sp = spouse_.lock()) {
std::cout << ",配偶: " << sp->name_;
} else {
std::cout << ",未婚";
}
std::cout << ",朋友: ";
for (const auto& wp : friends_) {
if (auto sp = wp.lock())
std::cout << sp->name_ << " ";
}
std::cout << std::endl;
}
const std::string& name() const { return name_; }
private:
std::string name_;
std::weak_ptr<Person> spouse_; // weak_ptr 而非 shared_ptr
std::vector<std::weak_ptr<Person>> friends_; // weak_ptr 观察朋友
};
int main() {
std::cout << "===== weak_ptr 打破循环引用 =====" << std::endl;
{
auto alice = std::make_shared<Person>("Alice");
auto bob = std::make_shared<Person>("Bob");
// 双向关联
alice->setSpouse(bob);
bob->setSpouse(alice);
alice->addFriend(bob);
bob->addFriend(alice);
alice->describe();
bob->describe();
std::cout << "\nAlice ref count: " << alice.use_count() << std::endl;
std::cout << "Bob ref count: " << bob.use_count() << std::endl;
// 如果 spouse_ 用 shared_ptr,引用计数相互加 1,离开作用域双方都不会被释放
// weak_ptr 避免了这个问题
}
std::cout << "\n--- 离开作用域后 ---" << std::endl;
std::cout << "Alice 和 Bob 都被正确释放" << std::endl;
return 0;
}
预期输出:
===== weak_ptr 打破循环引用 =====
构造: Alice
构造: Bob
Alice,配偶: Bob,朋友: Bob
Bob,配偶: Alice,朋友: Alice
Alice ref count: 1
Bob ref count: 1
析构: Bob
析构: Alice
--- 离开作用域后 ---
Alice 和 Bob 都被正确释放
逐段分析:
- 如果把
spouse_定义为shared_ptr<Person>,Alice 和 Bob 互相持有对方的 shared_ptr → 引用计数均为 2(alice+bob.spouse_/bob+alice.spouse_) - 离开作用域时,
alice和bob析构 → 引用计数各减 1 → 剩余引用计数 = 1(对方的 spouse_)→ 两者永不释放 → 内存泄漏 - 使用
weak_ptr后,spouse_ 不增加引用计数 → 离开作用域后两人引用计数均为 0 → 正确释放 friends_同样用weak_ptr——朋友关系是弱关联,不应阻止任何人被释放
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
| 循环引用(两个 shared_ptr 互相持有) | 内存泄漏 | 一方改用 weak_ptr |
shared_ptr(this) 在成员函数中 | 多个控制块 → 双重释放 | 继承 std::enable_shared_from_this |
shared_ptr<T>(new T) 用在函数参数 | 函数参数求值顺序不确定 → 泄漏 | 用 make_shared<T>() |
| weak_ptr 不检查就直接解引用 | 未定义行为 | 先 lock() 检查是否为空 |
| 在多线程中修改 shared_ptr | 数据竞争 | 用 std::atomic_...<shared_ptr> 或加锁 |
常见面试问题
shared_ptr 的引用计数是线程安全的吗?——引用计数的增减是线程安全的(原子操作),但 shared_ptr 对象本身的赋值和拷贝不是线程安全的。多线程读写同一个 shared_ptr 需要同步。
make_shared和shared_ptr(new T)的区别?——make_shared一次分配对象 + 控制块(内存连续、性能好);shared_ptr(new T)至少两次分配。make_shared异常安全——函数参数求值顺序不会导致泄漏。weak_ptr 的作用?——(1) 打破循环引用;(2) 缓存/观察者模式——不阻止对象释放;(3)
lock()安全地检查对象是否仍存活。enable_shared_from_this是做什么的?——让从this安全获取 shared_ptr 成为可能。内部用 weak_ptr 保存控制块信息,shared_from_this()返回引用同一控制块的 shared_ptr。shared_ptr 的删除器存储在哪里?何时调用?——存储在控制块中。当引用计数降为 0 时调用删除器释放对象;当弱引用计数也降为 0 时释放控制块。
小结
- shared_ptr:共享所有权 + 引用计数——最后一个释放时 delete 对象
make_shared:一次分配对象 + 控制块——性能更好,异常安全- weak_ptr:弱引用——不增加引用计数,
lock()检查对象存活 - 循环引用是 shared_ptr 的致命陷阱——用 weak_ptr 切断循环的一环
enable_shared_from_this:从 this 安全获取 shared_ptr——避免多控制块问题