标题:[C++] 智能指针 进阶
水墨不写bug

在很久之前我们探讨了智能指针
的浅显认识,接下来会更加深入,从源码角度认识智能指针,从而了解智能指针的设计原理,并应用到以后的工作项目中。
本文将会按照C++智能指针的发展历史,回顾智能指针的各个版本的设计。后半部分将会模拟实现最常考的shared_ptr的功能。
一、C++智能指针的发展历程
1. C++98时代:auto_ptr
的尝试与缺陷
背景 :C++早期依赖手动new
/delete
,稍微有管理不当比如忘记释放
,或者有异常抛出
(退出调用的函数栈,但是堆区的指针却找不到了 ),就可能导致内存泄漏。C++98引入auto_ptr
,首次尝试自动化资源管理。
机制 :基于RAII(资源获取即初始化),在构造时获取内存,在析构时自动释放内存
。
问题:
- 隐式所有权转移 :复制
auto_ptr
时,原指针变为nullptr
,导致难以追踪的悬空指针。 - 不兼容容器:因拷贝语义异常,无法安全用于STL容器。
示例:
cpp
auto_ptr<int> p1(new int(32));
auto_ptr<int> p2 = p1; // p1变为nullptr,后续使用p1导致未定义行为
结局 :最终C++11弃用,C++17移除。因此为了代码的逻辑性和健壮性,以及可移植性,非常不建议使用auto_ptr
。
2. Boost库的智能指针
背景 :C++社区通过Boost库探索更健壮的解决方案,后来这些方案被C++委员会收录到了C++11
。
关键类型:
scoped_ptr
:禁止拷贝,严格独占所有权,轻量且安全。shared_ptr
:引用计数实现共享所有权,解决多所有者场景。weak_ptr
:打破shared_ptr
循环引用,防止内存泄漏。
影响:直接为C++11智能指针奠定基础。
3. C++11
:C++划时代的进步
核心类型:
-
unique_ptr
:取代auto_ptr
,独占所有权,支持移动语义,禁止拷贝,可管理数组(unique_ptr<T[]>
)。cppunique_ptr<int> p1(new int(10)); unique_ptr<int> p2 = std::move(p1); // 显式所有权转移
-
shared_ptr
:引用计数共享资源,线程安全但性能有开销。cppshared_ptr<A> a = make_shared<A>(); shared_ptr<A> b = a; // 引用计数增至2
-
weak_ptr
:可通过其获取对应的shared_ptr
资源,不增加计数,需通过lock()
获取临时shared_ptr
。cppweak_ptr<A> w = a; if (shared_ptr<A> tmp = w.lock()) { /* 使用tmp */ }
改进:
- 弃用
auto_ptr
,推荐unique_ptr
。 - 引入
make_shared
:合并控制块与对象内存分配,提升性能与异常安全。
4. C++14:完善与便利性增强
-
make_unique
:填补make_shared
的对称性,安全构造unique_ptr
。cppauto p = make_unique<int>(20); // 避免显式new
-
shared_ptr
增强:支持自定义删除器与分配器,更灵活的资源管理。
后期的C++17用法不多常见,在此就不再赘述了。
二、智能指针的模拟实现
1、unique_ptr
cpp
template<class T>
class UniquePtr
{
UniquePtr<T>& operator=(const UniquePtr<T>&) = delete;//删除拷贝构造和赋值重载
UniquePtr(const UniquePtr<T>&) = delete;//这是unique_ptr的标志性的特点
public:
UniquePtr(T* ptr)
:_ptr(ptr)//浅拷贝
//这就要求我们需要在外部new出堆区的空间,然后传递给智能指针来管理
{}
~UniquePtr()
{
if(_ptr)//析构
delete _ptr;
}
T& operator*()//重载*和->让智能指针(类)像普通指针(自定义类型)一样使用
{
return *_ptr;
}
T* operator->()
{
return ptr;
}
private:
T* _ptr;
};
2、shared_ptr
cpp
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)//要求从外部传一个指向堆区的指针
,_refcount(new int{1})//开辟在堆上,便于所有的管理同一资源的智能指针对象维护
,_pmtx(new std::mutex)//同样的原因
{}
SharedPtr(const SharedPtr<T>& obj)
:_ptr(obj._ptr)//只需要浅拷贝,然后引用计数++
,_refcount(obj._refcount)
,_pmtx(obj._pmtx)
{
AddRef();
}
SharedPtr<T>& operator=(const SharedPtr<T>& obj)
{
if(obj._ptr != _ptr)//不能给自身赋值
{
Release();//释放掉被赋值的智能指针对象的原有资源
//浅拷贝
_ptr = obj._ptr;//两个指针指向同一个T*对象
_refcount = obj._refcount;//两个指针指向同一个int*
_pmtx = obj._pmtx;//两个指针指向同一个mutex*的锁
AddRef();
}
}
void AddRef()
{//增加引用计数需要加锁
_pmtx->lock();
(*_refcount)++;
_pmtx->unlock();
}
void Release()
{
_pmtx->lock();
bool f = false;
if(--(*_refcount) == 0 && _ptr)
{
f = true;
delete _ptr;
delete _refcount;
}
_pmtx->unlock();
//判断是否资源已经被释放,资源已经被释放,则释放锁
if(f == true)
{
delete _pmtx;
}
}
~SharedPtr()
{
Release();
}
int UseCount()
{
return *_refcount;
}
T& operator*()//重载运算符
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* GetPtr()
{
return _ptr;
}
private:
T* _ptr;//指向堆区资源的裸指针
int* _refcount;//开辟在堆区的引用计数
std::mutex* _pmtx;//堆区的锁,考虑线程安全
//如果设置在栈上,那么每一个对象都有一个独立的引用计数
//每一次自增自减都需要对同一引用计数的所有对象维护,非常难以维护
};
3、weak_ptr
库中的wear_ptr
的实现需要结合起来shared_ptr
,shared_ptr内部含有两个引用计数
,一个是记录管理资源的shared_ptr(占用引用计数)的个数,一个记录指向资源的weak_ptr(不占用引用计数)的个数。
在这里,为了简便而言,我们实现一个简化版本的weak_ptr,对上面的知识理解即可。
cpp
// 简化版本的weak_ptr实现
template<class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(nullptr)
{}
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp.get())
{}
WeakPtr<T>& operator=(const SharedPtr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
~完
转载请注明出处
