std::shared_ptr
- 当最后一个指向对象的
std::shared_ptr
被销毁时,引用计数变为零,对象被自动删除 - 循环引用是指两个或多个对象通过
std::shared_ptr
相互持有对方,导致它们的引用计数永远无法归零,从而造成内存泄露。解决方案是使用std::weak_ptr
来打破所有权循环
std::shared_ptr
的工作原理
std::shared_ptr
系统包含两个部分:
shared_ptr
对象本身:它像一个普通指针,指向被管理的对象- 控制块 (Control Block) :这是一个在堆上与被管理对象一起(或分开)分配的独立内存块。所有指向同一个对象的
shared_ptr
都会共享这一个控制块。控制块包含:- 强引用计数 (Strong Reference Count) :记录有多少个
shared_ptr
正在共享该对象。这是决定对象生死的关键 - 弱引用计数 (Weak Reference Count) :记录有多少个
weak_ptr
正在观察该对象 - 指向被管理对象的指针
- 强引用计数 (Strong Reference Count) :记录有多少个
工作流程:
- 当一个新的
shared_ptr
拷贝或赋值自另一个shared_ptr
时,强引用计数 +1 - 当一个
shared_ptr
被销毁或指向其他对象时,强引用计数 -1 - 当强引用计数降为零 时,
shared_ptr
会删除被管理的对象,并随后释放控制块
循环引用问题
当两个对象通过 shared_ptr
形成一个"引用环"时,它们的生命周期将永久相互依赖,无法被打破
c++
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next_;
Node() { std::cout << "Node created\n"; }
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
// 创建两个节点
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
// 让它们相互引用,形成一个环
node1->next_ = node2; // node2的引用计数变为2 (main里的node2 + node1里的next_)
node2->next_ = node1; // node1的引用计数变为2 (main里的node1 + node2里的next_)
std::cout << "Leaving main...\n";
} // main函数结束
main
函数结束,node1
(栈上的智能指针) 被销毁,它将Node A
的强引用计数从2减为1。但计数不为零,所以Node A
不会被删除main
函数结束,node2
(栈上的智能指针) 被销毁,它将Node B
的强引用计数从2减为1。但计数不为零,所以Node B
不会被删除- 最终,
Node A
持有Node B
的引用,Node B
持有Node A
的引用。它们在内存中相互"支撑",谁也无法被释放,导致内存泄露 。析构函数Node destroyed
从未被打印
std::weak_ptr
C++11 提供了 std::weak_ptr
来解决这个问题。weak_ptr
是一个"观察者",它不增加强引用计数
-
std::weak_ptr
:它指向由shared_ptr
管理的对象,但不拥有该对象。它的存在与否不影响对象的生命周期。它只是告诉:那个对象还在不在?
c++
#include <iostream>
#include <memory>
struct Node {
// next_仍然是强引用,代表"拥有"下一个节点
std::shared_ptr<Node> next_;
// prev_是弱引用,代表"观察"上一个节点,不增加引用计数
std::weak_ptr<Node> prev_;
Node() { std::cout << "Node created\n"; }
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next_ = node2; // node2的强引用计数为2
node2->prev_ = node1; // node1的强引用计数为1 (prev_是weak_ptr,不增加计数)
std::cout << "Leaving main...\n";
}
main
结束,栈上的node2
智能指针被销毁。Node B
的强引用计数从2减为1。不为零,不删除main
结束,栈上的node1
智能指针被销毁。Node A
的强引用计数从1减为0。Node A
被删除- 在
Node A
的析构函数中,其成员next_
(一个指向Node B
的shared_ptr
) 被销毁。Node B
的强引用计数从1减为0。Node B
被删除! - 内存被成功释放
如何使用 weak_ptr
? 因为 weak_ptr
不拥有对象,所以在访问它指向的对象之前,必须先用 lock()
方法将其提升为一个临时的 shared_ptr
,以确保在访问期间对象不会被销毁
c++
// 假设有一个 weak_ptr<Node> w_ptr;
if (auto s_ptr = w_ptr.lock()) { // lock()返回一个shared_ptr
// 如果对象还存在,s_ptr就是有效的
// 在这个if作用域内,可以安全地使用 s_ptr
} else {
// 对象已经被销毁
}