文章目录
-
- 前言
- 一、为什么需要弱引用指针?
-
- [1.1 封于修的智慧:既决高下,也决生死 vs 只旁观,不决生死](#1.1 封于修的智慧:既决高下,也决生死 vs 只旁观,不决生死)
- [1.2 核心设计目的](#1.2 核心设计目的)
- 二、std::weak_ptr的工作原理
-
- [2.1 内部结构解析](#2.1 内部结构解析)
- [2.2 weak_ptr的工作流程](#2.2 weak_ptr的工作流程)
- 三、核心应用场景与代码示例
-
- [3.1 场景一:解决循环引用问题](#3.1 场景一:解决循环引用问题)
- [3.2 场景二:缓存与资源管理](#3.2 场景二:缓存与资源管理)
- [3.3 场景三:观察者模式与Chrome中的PostTask](#3.3 场景三:观察者模式与Chrome中的PostTask)
- [3.4 场景四:Chrome中PostTask的典型用法](#3.4 场景四:Chrome中PostTask的典型用法)
- 四、std::weak_ptr的进阶用法
-
- [4.1 使用expired()快速检查](#4.1 使用expired()快速检查)
- [4.2 weak_ptr的自定义删除器](#4.2 weak_ptr的自定义删除器)
- 五、性能考虑与最佳实践
-
- [5.1 性能特点](#5.1 性能特点)
- [5.2 最佳实践](#5.2 最佳实践)
- [六 能否都用weak_ptr 不用shared_ptr?](#六 能否都用weak_ptr 不用shared_ptr?)
-
- 核心问题:谁来决定对象的生命周期?
- 代码示例说明
- 为什么必须有shared_ptr?
-
- [1. **生命周期管理需要"所有者"**](#1. 生命周期管理需要"所有者")
- [2. **引用计数机制要求**](#2. 引用计数机制要求)
- 正确的使用模式
- 类比理解
- 总结
前言
在C++现代编程中,智能指针已成为管理动态内存的基石。然而,许多开发者在接触std::shared_ptr和std::weak_ptr时,常常对后者的存在意义感到困惑。特别是在阅读大型项目源码时,频繁出现的weak_ptr用法更让人疑惑:既然已经有了强大的shared_ptr,为何还需要这种"弱不禁风"的指针?本文将从设计哲学、实现原理到实际应用场景,全面解析std::weak_ptr的奥秘,并通过丰富的代码示例展示其在实际工程中的价值。
一、为什么需要弱引用指针?
1.1 封于修的智慧:既决高下,也决生死 vs 只旁观,不决生死
正如封于修所言:std::shared_ptr既决高下,也决生死;std::weak_ptr只旁观,不决生死。这句话精辟地概括了两者的本质区别:
std::shared_ptr:拥有对象的所有权,通过引用计数决定对象的生死std::weak_ptr:观察对象的状态,但不拥有所有权,不参与生死决策
1.2 核心设计目的
std::weak_ptr的引入主要解决两大问题:
(1)打破循环引用,防止内存泄漏
这是最主要的设计初衷。当两个或多个对象通过shared_ptr相互引用时,会形成循环引用,导致引用计数永远无法归零,内存无法释放。
(2)安全地观察对象,避免悬空指针
在某些场景下,我们需要持有对象的引用,但又不希望因为我们的持有而阻止对象被销毁。weak_ptr提供了一种安全机制来检测对象是否仍然存活。如果对象还活着,就使用它;如果对象已经销毁,就可以感知到它没了,不访问野指针。std::weak_ptr 就是用来安全探测对象是否还存在。
所以std::weak_ptr要搭配std::shared_ptr使用。要理解 std::weak_ptr 的原理,得先看 std::shared_ptr 的内部结构。

内部结构示意图:

二、std::weak_ptr的工作原理
2.1 内部结构解析
要理解std::weak_ptr,必须先了解std::shared_ptr的内部结构。shared_ptr管理的堆内存包含两部分:
┌─────────────────────────────────────────┐
│ std::shared_ptr │
├─────────────────────────────────────────┤
│ ┌─────────────────┐ ┌───────────┐ │
│ │ 对象指针 │───│ 对象数据 │ │
│ └─────────────────┘ └───────────┘ │
│ ┌─────────────────┐ ┌───────────┐ │
│ │ 控制块指针 │───│ 控制块 │ │
│ └─────────────────┘ │ │ │
│ │ use_count │ │
│ │ weak_count│ │
│ │ ... │ │
│ └───────────┘ │
└─────────────────────────────────────────┘
控制块包含的关键计数器:
- use_count(强引用计数) :有多少个
shared_ptr指向该对象 - weak_count(弱引用计数) :有多少个
weak_ptr指向该对象
2.2 weak_ptr的工作流程
cpp
// 创建shared_ptr
auto sp = std::make_shared<MyClass>(); // use_count=1, weak_count=0
// 创建weak_ptr观察该对象
std::weak_ptr<MyClass> wp = sp; // use_count=1, weak_count=1
// 使用lock()安全访问
if (auto locked_sp = wp.lock()) { // use_count=2 (临时增加)
// 对象存活,安全使用
locked_sp->doSomething();
} // locked_sp析构,use_count恢复为1
// sp离开作用域
// use_count=0 → 对象被销毁
// weak_count=1 → 控制块保留
// wp离开作用域
// weak_count=0 → 控制块被销毁
- weak_ptr 的构造会指向跟 shared_ptr 相同的控制块,并把控制块的 weak_count 加 1。注意:不改变 use_count。
- weak_ptr 不能直接访问对象(没有 * 或 -> 操作符)。必须调用 .lock() 方法。
- .lock() 会检查控制块的 use_count。如果 use_count > 0(对象还活着):创建一个新的 shared_ptr 返回(此时 use_count +1),保证使用期间对象不会死。如果 use_count == 0(对象已死):返回一个空的 shared_ptr。
- weak_ptr 析构,把 weak_count 减 1。

这里面有一个非常精妙的 对象块和控制块分离 的设计细节:
- 对象释放: 所有 shared_ptr 离开作用域,use_count 归零,对象被 delete。
- 内存完全释放: 如果还有 weak_ptr 活着,控制块必须保留(因为 weak_ptr 还要去查计数器)。只有所有 weak_ptr 也离开作用域,weak_count 归零,控制块才会被 delete。
三、核心应用场景与代码示例
3.1 场景一:解决循环引用问题
cpp
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
public:
std::shared_ptr<Child> child;
~Parent() { std::cout << "Parent destroyed" << std::endl; }
void showChild() {
if (child) {
std::cout << "Parent has a child" << std::endl;
}
}
};
class Child {
public:
// 关键:使用weak_ptr而不是shared_ptr打破循环
std::weak_ptr<Parent> parent;
~Child() { std::cout << "Child destroyed" << std::endl; }
void showParent() {
// 安全地访问父对象
if (auto parent_sp = parent.lock()) {
std::cout << "Child has a parent" << std::endl;
parent_sp->showChild();
} else {
std::cout << "Parent no longer exists" << std::endl;
}
}
};
int main() {
std::cout << "=== 循环引用示例 ===" << std::endl;
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
// 建立双向关系
parent->child = child; // Parent拥有Child
child->parent = parent; // Child弱引用Parent
// 演示安全访问
child->showParent();
std::cout << "\n引用计数信息:" << std::endl;
std::cout << "Parent use_count: " << parent.use_count() << std::endl;
std::cout << "Child use_count: " << child.use_count() << std::endl;
// 离开作用域时,所有对象都能正常销毁
// 如果没有使用weak_ptr,这里会发生内存泄漏
return 0;
}
3.2 场景二:缓存与资源管理
cpp
#include <iostream>
#include <memory>
#include <unordered_map>
#include <string>
class Resource {
public:
std::string name;
Resource(const std::string& n) : name(n) {
std::cout << "Resource '" << name << "' created" << std::endl;
}
~Resource() {
std::cout << "Resource '" << name << "' destroyed" << std::endl;
}
void use() {
std::cout << "Using resource: " << name << std::endl;
}
};
class ResourceCache {
private:
// 缓存使用weak_ptr,不阻止资源销毁
std::unordered_map<std::string, std::weak_ptr<Resource>> cache;
public:
std::shared_ptr<Resource> getResource(const std::string& name) {
auto it = cache.find(name);
// 检查资源是否仍在缓存中且存活
if (it != cache.end()) {
if (auto resource = it->second.lock()) {
std::cout << "Cache hit: " << name << std::endl;
return resource; // 资源仍存活,直接返回
} else {
// 资源已被销毁,从缓存中移除
cache.erase(it);
}
}
// 缓存未命中,创建新资源
std::cout << "Cache miss, creating: " << name << std::endl;
auto newResource = std::make_shared<Resource>(name);
cache[name] = newResource; // 存储weak_ptr引用
return newResource;
}
void cleanup() {
// 清理已失效的缓存项
for (auto it = cache.begin(); it != cache.end(); ) {
if (it->second.expired()) {
std::cout << "Cleaning up expired: " << it->first << std::endl;
it = cache.erase(it);
} else {
++it;
}
}
}
size_t getCacheSize() const {
return cache.size();
}
};
int main() {
std::cout << "=== 缓存管理示例 ===" << std::endl;
ResourceCache cache;
// 第一次获取资源
auto res1 = cache.getResource("texture1");
res1->use();
{
// 第二次获取相同资源(应命中缓存)
auto res2 = cache.getResource("texture1");
res2->use();
std::cout << "\n当前缓存大小: " << cache.getCacheSize() << std::endl;
// res2离开作用域,但res1仍持有资源
}
std::cout << "\n释放res1..." << std::endl;
res1.reset(); // 释放资源
// 资源已被销毁,但缓存中仍有weak_ptr
std::cout << "清理前缓存大小: " << cache.getCacheSize() << std::endl;
cache.cleanup(); // 清理过期缓存
std::cout << "清理后缓存大小: " << cache.getCacheSize() << std::endl;
return 0;
}
3.3 场景三:观察者模式与Chrome中的PostTask
cpp
#include <iostream>
#include <memory>
#include <functional>
#include <vector>
#include <thread>
#include <chrono>
// 模拟Chrome中的异步任务场景
class TaskObserver {
public:
virtual ~TaskObserver() = default;
virtual void onTaskCompleted(int taskId) = 0;
};
class Worker {
private:
std::vector<std::weak_ptr<TaskObserver>> observers;
public:
// 注册观察者(使用weak_ptr避免阻止观察者销毁)
void addObserver(std::weak_ptr<TaskObserver> observer) {
observers.push_back(observer);
}
// 模拟异步任务执行
void performTask(int taskId) {
std::cout << "Worker: Starting task " << taskId << std::endl;
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Worker: Task " << taskId << " completed" << std::endl;
// 通知所有观察者
notifyObservers(taskId);
}
private:
void notifyObservers(int taskId) {
// 安全地通知所有观察者
for (auto it = observers.begin(); it != observers.end(); ) {
if (auto observer = it->lock()) {
// 观察者仍存活,调用回调
observer->onTaskCompleted(taskId);
++it;
} else {
// 观察者已被销毁,从列表中移除
std::cout << "Removing expired observer" << std::endl;
it = observers.erase(it);
}
}
}
};
class MyObserver : public TaskObserver {
private:
std::string name;
public:
MyObserver(const std::string& n) : name(n) {}
void onTaskCompleted(int taskId) override {
std::cout << name << ": Received completion of task " << taskId << std::endl;
}
};
int main() {
std::cout << "=== 异步任务观察者示例 ===" << std::endl;
Worker worker;
{
// 创建观察者
auto observer1 = std::make_shared<MyObserver>("Observer1");
auto observer2 = std::make_shared<MyObserver>("Observer2");
// 注册观察者
worker.addObserver(observer1);
worker.addObserver(observer2);
// 执行任务
worker.performTask(1);
// observer2离开作用域被销毁
std::cout << "\nDestroying observer2..." << std::endl;
}
// 再次执行任务,只有observer1会收到通知
std::cout << "\nPerforming another task..." << std::endl;
worker.performTask(2);
return 0;
}
3.4 场景四:Chrome中PostTask的典型用法
cpp
// 模拟Chrome中基于weak_ptr的异步任务提交
#include <iostream>
#include <memory>
#include <functional>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
class Task {
public:
virtual ~Task() = default;
virtual void execute() = 0;
};
class MyObject : public std::enable_shared_from_this<MyObject> {
private:
std::string name;
bool active = true;
public:
MyObject(const std::string& n) : name(n) {}
~MyObject() {
std::cout << name << ": Object destroyed" << std::endl;
}
void deactivate() {
active = false;
std::cout << name << ": Object deactivated" << std::endl;
}
// 安全提交任务的方法
void postTask(const std::string& taskName) {
// 获取weak_ptr用于任务提交
std::weak_ptr<MyObject> weak_this = shared_from_this();
// 模拟Chrome的PostTask
auto task = [weak_this, taskName]() {
// 尝试提升为shared_ptr
if (auto shared_this = weak_this.lock()) {
// 对象仍存活,执行任务
if (shared_this->active) {
std::cout << shared_this->name
<< ": Executing task '" << taskName << "'"
<< std::endl;
shared_this->processTask(taskName);
} else {
std::cout << "Task '" << taskName
<< "' skipped - object inactive" << std::endl;
}
} else {
// 对象已被销毁,安全地跳过任务
std::cout << "Task '" << taskName
<< "' skipped - object destroyed" << std::endl;
}
};
// 在实际的Chrome中,这里会将task提交到任务队列
std::cout << name << ": Task '" << taskName << "' posted" << std::endl;
// 模拟异步执行
std::thread([task]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
task();
}).detach();
}
private:
void processTask(const std::string& taskName) {
std::cout << name << ": Processing " << taskName << std::endl;
}
};
int main() {
std::cout << "=== Chrome风格PostTask示例 ===" << std::endl;
{
auto obj = std::make_shared<MyObject>("MyObject");
// 提交多个异步任务
obj->postTask("Task1");
obj->postTask("Task2");
// 在任务执行前销毁对象
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "\nDestroying object before tasks complete..." << std::endl;
}
// 给异步任务时间完成
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "\n=== 对象失效但未销毁的场景 ===" << std::endl;
auto obj2 = std::make_shared<MyObject>("Object2");
obj2->postTask("Task3");
// 使对象失效但不立即销毁
obj2->deactivate();
obj2->postTask("Task4");
// 给异步任务时间完成
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 0;
}
四、std::weak_ptr的进阶用法
4.1 使用expired()快速检查
cpp
#include <iostream>
#include <memory>
#include <vector>
class Resource {
public:
int id;
Resource(int i) : id(i) {
std::cout << "Resource " << id << " created" << std::endl;
}
~Resource() {
std::cout << "Resource " << id << " destroyed" << std::endl;
}
};
int main() {
std::cout << "=== expired()方法示例 ===" << std::endl;
std::weak_ptr<Resource> wp;
// 检查未初始化的weak_ptr
std::cout << "Initial state - expired: " << (wp.expired() ? "true" : "false") << std::endl;
{
auto sp = std::make_shared<Resource>(1);
wp = sp;
std::cout << "After assignment - expired: " << (wp.expired() ? "true" : "false") << std::endl;
// 使用lock()获取shared_ptr
if (auto locked = wp.lock()) {
std::cout << "Resource " << locked->id << " is alive" << std::endl;
}
}
// 对象已销毁
std::cout << "After scope - expired: " << (wp.expired() ? "true" : "false") << std::endl;
// 注意:expired()和lock()的竞态条件
std::cout << "\n=== 竞态条件演示 ===" << std::endl;
auto sp2 = std::make_shared<Resource>(2);
std::weak_ptr<Resource> wp2 = sp2;
// 在另一个线程中释放资源
std::thread([&sp2]() {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
sp2.reset();
}).detach();
// 主线程检查
std::this_thread::sleep_for(std::chrono::milliseconds(5));
// 这里存在竞态条件:expired()检查后,对象可能被销毁
if (!wp2.expired()) {
// 不安全:对象可能在这之后被销毁
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// 正确做法:直接使用lock()
if (auto safe_sp = wp2.lock()) {
std::cout << "Safe access: Resource " << safe_sp->id << std::endl;
} else {
std::cout << "Resource was destroyed after check" << std::endl;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
4.2 weak_ptr的自定义删除器
cpp
#include <iostream>
#include <memory>
class Resource {
public:
int* data;
Resource() {
data = new int[100];
std::cout << "Resource allocated with 100 integers" << std::endl;
}
~Resource() {
delete[] data;
std::cout << "Resource deallocated" << std::endl;
}
};
int main() {
std::cout << "=== 自定义删除器示例 ===" << std::endl;
// 创建带有自定义删除器的shared_ptr
auto deleter = [](Resource* res) {
std::cout << "Custom deleter called" << std::endl;
delete res;
};
std::shared_ptr<Resource> sp(new Resource(), deleter);
std::weak_ptr<Resource> wp = sp;
std::cout << "use_count: " << sp.use_count() << std::endl;
std::cout << "weak_count: " << sp.use_count() << std::endl;
// 释放shared_ptr
sp.reset();
// weak_ptr仍然可以检测对象状态
std::cout << "After reset - expired: " << wp.expired() << std::endl;
return 0;
}
五、性能考虑与最佳实践
5.1 性能特点
- 内存开销 :
weak_ptr本身很小(通常两个指针大小),但会增加控制块的weak_count - 操作成本 :
lock()操作需要原子操作检查use_count,有一定开销 - 适用场景:适合生命周期不确定或需要避免循环引用的场景
5.2 最佳实践
cpp
// 良好实践示例
class GoodPractice {
private:
std::weak_ptr<Dependency> dependency_;
public:
void setDependency(std::shared_ptr<Dependency> dep) {
dependency_ = dep; // 存储weak_ptr而不是shared_ptr
}
void useDependency() {
// 正确:使用lock()获取临时shared_ptr
if (auto dep = dependency_.lock()) {
dep->use();
}
// lock()返回的shared_ptr离开作用域,不延长生命周期
}
};
// 避免的实践
class BadPractice {
private:
std::shared_ptr<Dependency> dependency_; // 错误:不必要的所有权
public:
void useDependency() {
if (dependency_) {
dependency_->use();
}
// 即使不再需要,dependency_仍保持对象存活
}
};
六 能否都用weak_ptr 不用shared_ptr?
核心问题:谁来决定对象的生命周期?
关键点:weak_ptr本身不拥有对象的所有权,它只是一个"观察者"。如果所有指针都是weak_ptr,就没有任何指针负责保持对象存活!
代码示例说明
cpp
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
std::cout << "=== 如果全部使用weak_ptr会发生什么 ===" << std::endl;
// 尝试只用weak_ptr,不用shared_ptr
std::weak_ptr<MyClass> wp;
{
// 必须先用shared_ptr创建对象
auto sp = std::make_shared<MyClass>(); // 对象诞生
wp = sp; // weak_ptr开始观察
// 此时对象是存活的
if (auto temp = wp.lock()) {
temp->doSomething(); // 正常工作
}
// sp离开作用域,对象立即被销毁!
// 因为没有任何shared_ptr拥有它了
}
// 现在wp观察的对象已经不存在了
if (auto temp = wp.lock()) {
// 这里永远不会执行
temp->doSomething();
} else {
std::cout << "对象已经被销毁了!" << std::endl;
}
return 0;
}
为什么必须有shared_ptr?
1. 生命周期管理需要"所有者"
shared_ptr是所有者 - 决定对象何时被销毁weak_ptr是观察者 - 只能观察,不能决定生死
cpp
// 错误示例:试图只用weak_ptr
std::weak_ptr<MyClass> createObject() {
// 无法只用weak_ptr创建对象!
// 必须先用shared_ptr创建
auto sp = std::make_shared<MyClass>();
return sp; // 转换为weak_ptr返回
}
void problematicExample() {
auto wp = createObject();
// 对象在createObject()返回时立即被销毁!
// 因为sp离开作用域,引用计数归零
if (auto obj = wp.lock()) {
obj->doSomething(); // 永远不会执行!
}
}
2. 引用计数机制要求
cpp
// 控制块的生命周期
// use_count = 0 → 对象被销毁
// weak_count = 0 → 控制块被销毁
// 如果只有weak_ptr:
// use_count永远为0 → 对象立即被销毁
// weak_ptr无法将use_count从0变为1
正确的使用模式
模式1:主从关系
cpp
class Manager {
private:
std::shared_ptr<Resource> resource_; // 管理者拥有资源
public:
std::weak_ptr<Resource> getResource() {
return resource_; // 返回weak_ptr给使用者
}
};
class User {
private:
std::weak_ptr<Resource> resource_; // 使用者只观察资源
public:
void useResource() {
if (auto res = resource_.lock()) {
res->use(); // 安全使用
}
}
};
模式2:工厂模式
cpp
class ObjectFactory {
private:
std::unordered_map<int, std::weak_ptr<MyObject>> cache_;
public:
std::shared_ptr<MyObject> createOrGet(int id) {
// 先检查缓存
if (auto it = cache_.find(id); it != cache_.end()) {
if (auto obj = it->second.lock()) {
return obj; // 返回存活的共享对象
} else {
cache_.erase(it); // 清理过期条目
}
}
// 创建新对象(必须有shared_ptr!)
auto newObj = std::make_shared<MyObject>(id);
cache_[id] = newObj; // 缓存weak_ptr引用
return newObj;
}
};
类比理解
可以把shared_ptr和weak_ptr的关系类比为:
shared_ptr就像房子的房主 - 决定房子何时拆除weak_ptr就像房子的访客 - 可以来访,但不能决定房子命运
如果所有人都只是访客(weak_ptr),没有人当房主(shared_ptr),那么房子从一开始就不会存在,或者会立即被拆除!
不能全部使用weak_ptr的原因:
- 生命周期管理 :必须有至少一个
shared_ptr来"拥有"对象,决定其生命周期 - 对象创建 :
weak_ptr不能单独创建对象,必须依赖shared_ptr - 引用计数 :
use_count必须大于0对象才能存活,而weak_ptr不增加use_count
正确的设计原则:
- 需要拥有 对象时用
shared_ptr - 只需要观察 对象时用
weak_ptr - 两者配合使用,各司其职
这就是为什么在Chrome的PostTask中你会看到两者配合使用:对象本身由shared_ptr管理生命周期,而异步任务通过weak_ptr来安全地访问对象。
总结
std::weak_ptr是C++智能指针体系中不可或缺的一环,它体现了"观察而非拥有"的设计哲学。通过本文的深入分析,我们可以总结出以下几点:
-
核心价值 :
weak_ptr主要解决循环引用和悬空指针两大问题,在复杂对象关系中起到关键作用。 -
工作原理 :通过共享
shared_ptr的控制块,维护独立的弱引用计数,实现对象生命周期的安全观察。 -
Chrome中的应用 :在异步编程模型中,
weak_ptr确保了任务执行时对象的安全访问,避免了因任务队列延迟导致的野指针访问。 -
使用原则:
- 当需要观察对象但不拥有所有权时使用
weak_ptr - 总是通过
lock()方法安全地访问对象 - 在缓存、观察者模式、打破循环引用等场景中优先考虑
- 当需要观察对象但不拥有所有权时使用
-
设计启示 :
weak_ptr的成功在于它平衡了灵活性与安全性,为C++大型项目提供了可靠的生命周期管理方案。
理解并熟练运用weak_ptr,是成为高级C++开发者的重要标志。它不仅是解决特定问题的工具,更是一种设计思想的体现------在软件架构中,明确的所有权关系和安全的生命周期管理是构建稳定、可维护系统的基石。