C++11智能指针
C++98中的auto_ptr
存在诸多问题,未被广泛使用。在C++11标准库中真正引入了智能指针,包括unique_ptr
,shared_ptr
和weak_ptr
,智能指针的设计初衷就是为了帮助开发者管理内存。
unique_ptr
std::unique_ptr<T>
比std::shared_ptr<T>
具有更小的内存,而且不需要维护引用计数,因此它的性能更好。当我们需要一个独占的指针时,应该优先使用unique_ptr
。
特性
字面意思,unique_ptr
最大的特性就是独占所有权,即同一时间只能有一个 unique_ptr
拥有某个对象的所有权。它能够自动管理内存并在不再使用时释放资源,从而避免内存泄漏。
- 独占所有权:独占所指对象的所有权,不能共享;
- 不能复制:禁止拷贝,确保了资源的唯一所有权;
- 自动销毁:当
unique_ptr
离开其作用域时(如函数结束或对象被销毁),它会自动释放所指向的资源,不需要显式调用delete
; - 轻量高效:相比于
shared_ptr
,unique_ptr
没有额外的引用计数开销。
特性实现
独占所有权
禁止拷贝:
// 删除了拷贝构造函数,确保同一时间只有一个 MyUniquePtr 对象拥有资源的所有权,防止拷贝操作
MyUniquePtr(const MyUniquePtr& other) = delete;
// 删除了拷贝赋值操作符,确保不会通过赋值创建多个 MyUniquePtr 对象同时管理同一资源
MyUniquePtr& operator=(const MyUniquePtr& other) = delete;
移动语义:
// 移动构造函数:转移所有权
MyUniquePtr(MyUniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr; // 将源指针置空,确保只有一个指针管理资源
}
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) { // 防止自我赋值
delete ptr; // 释放当前持有的资源
ptr = other.ptr; // 转移新资源的所有权
other.ptr = nullptr; // 将源指针置空
}
return *this;
}
自动销毁
析构释放:
// 析构函数:释放管理的资源
~MyUniquePtr() {
delete ptr; // 自动删除所管理的对象
}
访问对象
// 重载 * 操作符,方便访问对象
T& operator*() const {
return *ptr;
}
// 重载 -> 操作符,方便访问对象的成员
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
// 放弃所有权,不删除对象,并返回指针
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
shared_ptr
通常用于一些资源创建昂贵比较耗时的场景, 比如涉及到文件读写、网络连接、数据库连接等。当需要共享资源的所有权时,例如,一个资源需要被多个对象共享,但是不知道哪个对象会最后释放它,这时候就可以使用std::shared_ptr<T>
。
特性
不同于unique,shared_ptr
可以共享所有权,并引入了引用计数特性。
- 共享所有权:多个
shared_ptr
对象可以共享管理同一个资源; - 引用计数:维护一个引用计数,每当有新的
shared_ptr
复制或移动该资源时,引用计数会增加;当某个shared_ptr
被销毁时,引用计数减少。只有当引用计数为零时,资源才会被释放; - 线程安全的引用计数:
shared_ptr
的引用计数操作是线程安全的,因此它可以安全地在多线程环境下使用; - 联动
weak_ptr
:与weak_ptr
协作,防止循环引用的问题。
特性实现
共享所有权
MySharedPtr
支持拷贝构造和赋值操作,通过增加引用计数实现共享所有权。
// 拷贝构造函数,增加引用计数
MySharedPtr(const MySharedPtr& other) : ptr(other.ptr), ref_count(other.ref_count) {
(*ref_count)++; // 引用计数增加
std::cout << "Copied shared_ptr, ref_count = " << *ref_count << std::endl;
}
引用计数
ref_count
用于跟踪有多少个 MySharedPtr
对象共享同一个资源。每当有新的 MySharedPtr
对象拷贝构造时,引用计数增加;当一个对象被销毁时,引用计数减少。
// 构造函数
explicit MySharedPtr(T* p = nullptr) : ptr(p), ref_count(new int(1)) {
std::cout << "Created shared_ptr, ref_count = 1" << std::endl;
}
// 赋值运算符,处理引用计数
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
// 先减少当前对象的引用计数
release();
// 复制新对象的指针和引用计数
ptr = other.ptr;
ref_count = other.ref_count;
(*ref_count)++; // 引用计数增加
std::cout << "Assigned shared_ptr, ref_count = " << *ref_count << std::endl;
}
return *this;
}
线程安全
shared_ptr
使用了原子操作来管理其引用计数。因此,当多个线程同时复制、销毁或重新赋值 shared_ptr
时,引用计数的增减操作是原子性的,不会发生竞态条件(race condition)。
weak_ptr
常用于数据结构中防止 shared_ptr
之间的循环依赖。例如在树结构、图结构或观察者模式中,经常使用 weak_ptr
来防止内存泄漏。当你不希望影响对象生命周期,但需要临时访问某个对象时,可以使用 weak_ptr
。
特性
- 不影响引用计数:
weak_ptr
不会增加shared_ptr
的强引用计数。这意味着,即使有weak_ptr
指向某个对象,当所有shared_ptr
都销毁时,该对象仍然会被释放。 - 避免循环引用:在复杂的数据结构中,两个对象可能互相引用。如果双方都使用
shared_ptr
,则会产生循环引用,导致内存泄漏。使用weak_ptr
可以解决这个问题,因为weak_ptr
不会阻止对象的销毁。 - 只能通过
lock()
获取对象:由于weak_ptr
不直接拥有对象,它无法直接访问被引用的对象。需要调用lock()
方法将其转换为shared_ptr
,这样可以确保在访问对象时对象仍然存在。
特性实现
不影响生命周期
weak_ptr
和 shared_ptr
共享同一个控制块,这个控制块包含了两个计数器:
- 强引用计数:跟踪有多少个
shared_ptr
实例引用同一个对象。 - 弱引用计数:跟踪有多少个
weak_ptr
引用该对象。
控制块不仅存储对象的引用计数,还保存着对象的指针(对象的地址)。当 shared_ptr
引用的对象被销毁时,控制块不会立即被释放,因为 weak_ptr
可能还在使用它。
weak_ptr
不会增加对象的强引用计数,因此它不会影响对象的生命周期。即使有多个 weak_ptr
引用该对象,当所有的 shared_ptr
被销毁时,强引用计数为 0,资源会被释放。
这通过引用计数的拆分实现,weak_ptr
只影响弱引用计数,不会干涉 shared_ptr
的强引用计数。
weak_ptr() : ptr_(nullptr), ref_count_(nullptr), weak_count_(nullptr) {}
weak_ptr(const shared_ptr<T>& sharedPtr) : ptr_(sharedPtr.ptr_), ref_count_(sharedPtr.ref_count_), weak_count_(sharedPtr.weak_count_) {
if (weak_count_) (*weak_count_)++;
}
避免循环引用
weak_ptr
最典型的用途是解决循环引用问题。在两个对象互相引用时,如果都使用 shared_ptr
,对象将无法自动释放,因为它们的强引用计数永远不会降到 0。weak_ptr
通过不增加强引用计数来打破这个循环,使对象在强引用计数归零时能够被正确销毁。
访问对象
weak_ptr
没有直接访问对象的能力,需要通过 lock()
将自己转换为一个临时的 shared_ptr
来访问对象。
lock()
的实现原理是检查强引用计数是否大于 0。如果大于 0,表示对象还存在,于是 lock()
返回一个新的 shared_ptr
,并增加强引用计数;如果强引用计数为 0,lock()
返回一个空的 shared_ptr
。
shared_ptr<T> lock() const {
// 检查强引用计数是否大于 0,如果大于 0,表示对象仍然存在
if (ref_count_ && *ref_count_ > 0) {
return shared_ptr<T>(*this); // 创建并返回一个新的 shared_ptr
}
return shared_ptr<T>(nullptr); // 否则返回一个空的 shared_ptr
}