03 智能指针
在学 C 语言的过程中,我们知道普通指针管理内存容易造成内存泄漏:
T* p = new T[n];
delete[] p;
由于手动 delete 容易遗忘而造成内存泄漏,因此 C++ 提供了智能指针用于内存的管理。
智能指针的实现基于 RAII(Resource Acquisition Is Initialization)思想,即将资源的生命周期与对象的生命周期绑定:对象创建时获取资源,对象销毁时自动释放资源,从而避免手动管理内存带来的错误。本质上来说,智能指针是一个对象。
智能指针的相关内容包含在头文件 <memory> 中。
标准库中支持的智能指针类型有三种:
unique_ptrshared_ptrweak_ptr
unique_ptr
std::unique_ptr 只允许这个指针来管理对应的资源, 不允许进行指针的赋值和拷贝 ,但是可以用 move 方法来移动指针的所有权 ,比如 unique_ptr<int> p2 = move(p1);
若某个对象A拥有另一个对象B的所有权,那么A就负责B的生命周期管理,即B的创建、使用和销毁。换句话说,A有权决定B什么时候被创建,什么时候被销毁。
auto p1 = std::make_unique<int>(42);
auto p2 = p1; // 编译错误
auto p1 = std::make_unique<int>(42);
auto p2 = std::move(p1); // 编译成功
如果希望转移所有权,则需要使用移动语义(Move Semantics),std::move():
auto p1 = std::make_unique<int>(42);
auto p2 = std::move(p1);
此时对象的所有权从 p1 转移给 p2,而 p1 会变为空指针(nullptr)。
shared_ptr
std::shared_ptr 是非独占的指针,可以多个指针管理一个对象。内部使用 引用计数。shared_ptr内部的引用计数是 线程安全 的,但是对象的读取需要 加锁。
引用计数记录当前有多少个 shared_ptr 正在管理同一个对象。
- 创建第一个
shared_ptr时,引用计数为 1。 - 每拷贝一个
shared_ptr,引用计数 加 1。 - 每析构一个
shared_ptr,引用计数 减 1。
当引用计数减为 0 时,说明已经没有任何 shared_ptr 指向该对象,对象会被自动释放。
#include <iostream>
#include <memory>
void foo() {
auto ptr = std::make_shared<int>(42); // 创建一个 shared_ptr,指向整数 42
{
auto another = ptr; // another 与 ptr 共享所有权
std::cout << *another << std::endl; // 输出 42
} // another 超出作用域,但整数不会释放,因为 ptr 仍然指向它
std::cout << *ptr << std::endl; // 仍然可以输出 42
} // ptr 超出作用域,最后一个 shared_ptr 被销毁,整数自动释放
void bar() {
auto ptr = std::make_shared<int>(100); // 创建一个 shared_ptr,指向整数 100
auto copy = ptr; // 再创建一个 shared_ptr,共享所有权
std::cout << *ptr << std::endl; // 输出 100
std::cout << *copy << std::endl; // 输出 100
} // ptr 和 copy 同时离开作用域,对象自动释放
可以用 shared_ptr可以使用use_count 函数来查看某个对象拥有的指针的个数
不能用一个原始指针初始化多个 shared_ptr 否则会造成内存多次释放。
#include <iostream>
#include <memory>
int main() {
int* raw = new int(42);
std::shared_ptr<int> ptr1(raw);
std::shared_ptr<int> ptr2(raw); // 错误:再次用同一个裸指针构造
std::cout << *ptr1 << std::endl;
std::cout << *ptr2 << std::endl;
}
shared_ptr通过拷贝已有的 shared_ptr才能够实现多个shared_ptr管理同一个对象。而同一个裸指针只能用于构造一个 shared_ptr。
weak_ptr
std::weak_ptr 是一种弱引用(Weak Reference)。
它可以观测一个由 std::shared_ptr 管理的对象,但并不拥有对象的所有权。因此,weak_ptr 不会增加引用计数,也不会影响对象的生命周期。
与 shared_ptr 不同,weak_ptr 不能直接访问对象,而需要先转换为 shared_ptr。
假设已经存在一个 shared_ptr:
auto ptr = std::make_shared<int>(42);
如果只是想判断对象是否仍然存在:
std::weak_ptr<int> observer = ptr;
weak_ptr不能直接使用 * 或 ->。而且必须调用 lock() 获得一个 shared_ptr 后才能访问对象。
auto ptr = std::make_shared<int>(42);
std::weak_ptr<int> observer = ptr;
if (auto temp = observer.lock()) {
std::cout << *temp << std::endl;
}