目录
一、普通指针
int *ptr;
存在的缺陷:
1.需要手动对指向的内存进行资源释放,否则会出现内存泄漏;
2.多个指针指向相同的内存地址,有一个指针对资源释放后,若没有对其他指针进行置空,那么容易产生访问内存异常不可控的错误;
二、智能指针
1.带引用计数的智能指针
带引用计数的智能指针则可以同时多个指向同一资源。
刚开始引用计数为3(有三个指针指向new对象),当ptr1不指向new对象时,引用计数减一为2,当全部指针不指向对象new时,将对象new释放掉。
带引用计数的智能指针包括 shared_ptr 和 weak_ptr。
1.1shared_ptr
shared_ptr 一般称为强智能指针,一个 shared_ptr 对资源进行引用时,资源的引用计数会增加一,通常用于管理对象的生命周期。只要有一个指向对象的 shared_ptr 存在,该对象就不会析构。上图中引用计数的工作过程就使用了 shared_ptr。
1.2 weak_ptr
weak_ptr 一般被称为弱智能指针,其对资源的引用不会引起资源的引用计数的变化,通常作为观察者,用于判断资源是否存在,并根据不同情况做出相应的操作。
比如使用 weak_ptr 对资源进行弱引用,当调用 weak_ptr 的 lock 方法时,若返回 ptr,则说明资源已经不存在,放弃对资源继续操作。否则,将返回一个 shared_ptr 对象,可以继续操作资源。
另外,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象也还是会被释放。
1.3小结
- shared_ptr 会增加资源的引用计数,常用于管理对象的生命周期。
- weak_ptr 不会增加资源的引用计数,常作为观察者用来判断对象是否存活。
2.不带引用计数的智能指针
不带引用计数的智能指针包括 auto_ptr 、scoped_ptr和unique_ptr。
2.1 auto_ptr
auto_ptr 模板定义了类似指针的对象,将 new 获得的地址赋给该对象。当 auto_ptr 对象过期时,析构函数将使用 delete 来释放内存。如果将 new 返回的地址赋值给 auto_ptr 对象,无须记住还需要释放这些内存。在 auto_ptr 对象过期时,内存将自动被释放。
template<class _Ty>class auto_ptr
{
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty * _Ptr=ptr) noexcept : _Myptr(_Ptr)//初始化列表
{
//构造函数
}
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release)
{
//拷贝构造函数,会调用release函数
}
_Ty * release noexcept {
/*使用拷贝构造时,最后一个auto_ptr持有资源, 其余被置为ptr*/
_Ty * _Tmp = _Myptr;
_Myptr = ptr;
return (_Tmp);
}
private:
_Ty * _Myptr;//指向资源
};
使用拷贝构造时,如果只有最后一个 auto_ptr 持有资源,其余 auto_ptr 持有的资源会被置为 ptr。因此需要注意,不能在容器中使用 auto_ptr,当容器发生拷贝时,原容器中 auto_ptr 持有的资源会置 ptr
当试图调用 auto_ptr 的拷贝构造函数时,在初始化列表中调用了 release 函数,release 函数用一个 _Tmp 指针保存资源并返回用于初始化当前的 auto_ptr 的类成员 _Myptr,而 _Right 对应的 _Myptr 被置为 ptr
只能对 new 分配的内存使用 auto_ptr 对象,不能对由 new() 分配的或通过声明变量分配的内存使用它。
auto_ptr <double> pd;
double *p_reg = new double;
pd = p_reg; // 不允许
pd = auto_ptr <double> (p_reg); //允许
auto_ptr <double> panto =p_reg; //不允许
auto_ptr <double> pauto (p_reg); //允许
示例:
#include<iostream>
#include<memory>
using namespace std;
int main()
{
//定义auto_ptr指针ptr
std::auto_ptr<int> ptr(new int(6));
//拷贝构造ptr定义ptr1
std::auto_ptr<int> ptr1(ptr);
//此时ptr已经为空指针,对空指针ptr赋值会产生不可预料的错误
*ptr=8;
return 0;
}
开始时 ptr 指向new出来的数字 6的地址,当用 ptr1 拷贝构造 ptr 时,ptr1 指向6的地址,而 ptr 则指向 NULL。下一行程序中如果对空指针 ptr 赋值 8,将会产生不可预料的错误。
2.2 scoped_ptr
与auto_ptr 不同,scoped_ptr私有化了拷贝构造函数和赋值函数,因此,资源的所有权无法进行转移,也无法在容器中使用,保证了资源的所有权。
template<class T> class scoped_ptr
{
private: //私有化
T * px;
scoped_ptr(scoped_ptr const &);//拷贝构造函数
scoped_ptr & operator=(scoped_ptr const &);//赋值构造函数
public:
typedef T element_type;
explicit scoped_ptr( T * p = ptr ): px( p ) { }
~scoped_ptr //析构函数
};
scoped_ptr 将拷贝构造函数和赋值构造函数定义为private权限 来阻止浅拷贝的发生。
scoped_ptr<int> sp1(new int(6));//初始化sp1指针,正确
scoped_ptr<int> sp2(sp1);//错误,无法拷贝构造,因为 scoped_ptr 私有化了拷贝构造函数,无法显式调用
scoped_ptr<int> sp3(new int(5));//初始化sp3指针
sp1=sp3;//错误,无法赋值,因为 scoped_ptr 私有化了赋值构造函数
auto_ptr 是通过将除最后一个以外的其它 auto_ptr 置 ptr 来避免浅拷贝的发生,它的资源所有权是可以转移的。而 scoped_ptr 是直接禁止了拷贝与赋值,资源所有权无法转移。
2.3unique_ptr
unique_ptr 删除了拷贝构造函数和赋值函数,因此不支持普通的拷贝或赋值操作。
template<class _Ty,class _Dx>class unique_ptr: public _Unique_ptr_base<_Ty, _Dx>
{
public:
typedef _Unique_ptr_base<_Ty, _Dx> _Mybase;
typedef typename _Mybase::pointer pointer;
typedef _Ty element_type;
typedef _Dx deleter_type;
unique_ptr(unique_ptr&& _Right) noexcept : _Mybase(_Right.release, _STD forward<_Dx>(_Right.get_deleter))
{
// 右值引用的拷贝构造函数
}
unique_ptr& operator=(unique_ptr&& _Right) noexcept
{
//提供了右值引用的operator=赋值构造函数
if (this != _STD addressof(_Right))
{
reset(_Right.release);
this->get_deleter = _STD forward<_Dx> (_Right.get_deleter);
}
return (*this);
}
/* 删除了unique_ptr的拷贝构造和赋值函数,拒绝浅拷贝 */
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
unique_ptr 和 scoped_ptr 一样禁止了拷贝构造和赋值构造,引入了带右值引用的拷贝构造和赋值。可以把 unique_ptr 作为函数的返回值。
unique_ptr<int> p1(new int(6));//正确写法
unique_ptr<int> p2(p1); //这么写是错误的: unique_ptr不支持拷贝
unique_ptr<int> p3;
p3=p2;//这么写是错误的:unique_ptr不支持赋值
2.4 小结:
不带引用计数的智能指针有:auto_ptr 、scoped_ptr、unique_ptr
- 相同点:最终只有一个智能指针持有资源。
- 不同点:
-
- auto_ptr 进行拷贝构造时,会将之前的 auto_ptr 的ptr 置空;
- scoped_ptr 通过私有化了拷贝构造和赋值函数杜绝浅拷贝;
- unique_ptr 通过删除了拷贝构造和赋值函数函数杜绝浅拷贝,但引入了带右值引用的拷贝构造和赋值函数
-