1.RAlI与智能指针
在了解智能指针的使用之前,让我们先明了为何我们需要使用智能指针技术
1.1 RALL
RAll (Resource Acquisition ls lnitialization) 是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化-即使用局部对象来管理资源的技术;这里的资源主要是指操作系统中有限的东西,如内存(heap)、网络套接字,互斥量,文件句柄等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。
资源的使用经历三个步骤
- 获取资源(创建资源)
- 使用资源
- 销毁资源(析构对象)
RAII方案利用局部对象自动销毁的特点来控制资源的生命周期,基于这样的技术实现的智能指针,能通过自动销毁的特性,避免cpp程序员忘记主动销毁资源而引发不必要的问题。
用大白话讲,智能指针会自动销毁,这样我们就不用担心忘记使用delete或重复调用delete所引发的内存管理问题。
智能指针的出现解决了许多传统指针具有的问题,见下文
1.2 使用传统指针所带来的问题
直接使用传统的指针管理内存会存在以下问题
总而言之,使用传统的指针在cpp中存在很大的内存管理风险
可以说,相比于传统指针,智能指针:
- 1.解决了空指针与野指针的问题
智能指针在cpp中是一个对象,其拥有构造函数,通常情况下,在构造智能指针时需要传入要指向的已存在的对象实体,也因此智能指针不存在空指针(*p = NULL)以及野指针(单创建指针而未指明指向的对象,如int* p
;p为一个野指针)的情况。 - 2.解决对象重复释放的问题
智能指针在其生命周期结束时,调用析构函数来实现对指向资源的释放,避免对象不必要的多次释放。 - 3.解决了内存泄漏问题
同上,使用智能指针可避免忘记释放内存导致的内存泄漏问题。
2. C++中的智能指针
C++中拥有如下三种智能指针(auto_ptr已经弃用)
我们从auto_pt被弃用的原因入手,从这个“最基础”的智能指针开始,我们可以了解到为何其功能会不全面,从而帮助我们理解为何需要有其他三种智能指针,以及它们各自所拥有的“新”特性(相对auto_ptr而言)。
1.2auto_ptr及其弃用的原因
下面的代码实例实现了一个auto_ptr
template<class _Ty>
class my_auto_ptr
{
private:
bool _Owns; //
_Ty* _Ptr; //
public:
my_auto_ptr(_Ty* p = NULL):_Owns(p != NULL),_Ptr(p){}
~my_auto_ptr()
{
if(_Owns){ delete _Ptr;}
_Owns = false;
_PTr = NULL;
}
_Ty* get() const
{ return _Ptr; }
//以&返回是为得到_Ptr所指向的实体
_Ty& operator* () const
{ return*(get(); }
_Ty* operator->() const
{ return get(); }
my_auto_ptr& operalor=(const my_auto_ptr& _Y)
{
if (this == &_Y) return *this;
if (_Owns)
{ delete _Ptr; }
_Owns = _Y._Owns;
_Ptr = _Y.release() ;
return 0;
}
};
void fun {
my_auto_ptr<Object>obj(new Object (10));
cout <<obj->Value() << endl ;
cout <<(*obj).Value()<< endl;
}
弃用的原因
- 1.拷贝构造函数与赋值语句的意义不明确
如上例中使用浅拷贝(共享拥有权Owns)会致使同一空间被释放两次,引发程序崩溃;
如果不共享拥有权,改为使用release()
函数来实现拥有权的转移.由此在执行赋值或拷贝构造后,原指针的_Owns被设为false,无法再通过原指针访问其对应资源,这有时并不是我们想要的结果.
- 2.仍未解决传统数组中,对单个对象delete和对数组指针delete[]的冲突问题
int main()
{
my_auto_ptr<int> pobja (new int[10]);//T
my_auto_ptr<Object> pobjb(new Object[10]);//F,显然my_auto_ptr中的析构函数无法应对这种情况
return 0;
}
auto_ptr因为无法解决上述问题,使得auto_ptr无法与STL库中的容器关联
auto_ptr最终于C++11标准中被弃用.于C++17标准中被移除
1.3unique_ptr(唯一型智能指针) 的使用
1.3.1对使用权问题的解决
为解决auto_ptr中使用权Owns的不明确问题,因而引入unique_ptr(唯一型智能指针),该指针相对于auto_ptr不具有bool _Owns
,其对资源的使用权具有唯一性,unique_ptr唯一拥有其指向的资源空间(因此不需添加bool _Owns
对其标致)
#include<memory> //智能指针存于此库中
template<class _Ty>
class my_unique_ptr
{
private:
_Ty*_Ptr;
my_unique_ptr(const my_unique_ptr&) = delete; //禁用了拷贝构造函数
my_unique_ptr& operator=(const my_unique_ptr&) = delete;//禁用了赋值语句重载
public:
typedef _Ty*pointer;
typedef _Ty element_type;
my_unique_ptr(_Ty* p = NULL):_Ptr(p){}
~my_unique_ptr(){ delete _Ptr;}
//移动构造
my_unique_ptr(my_unique_ptr&&_Y)
{
Ptr = _Y._Ptr;
_Y._Ptr = nullptr;
}
//移动赋值
my_unique_ptr& operator=(my_unique_ptr&&_Y)
{
if(this != &_Y)
{
delete _ptr;
Ptr = _Y._Ptr;
Y.Ptr = NULL;
}
return *this;
}
};
//F,unique_ptr禁用了拷贝构造函数
void fun(std::unique_ptr<Object> test)
{···}
int main()
{
std::unique_ptr<int> pobja (new int[10]);//T
std::unique_ptr<Object> pobjb(pobja);//F,unique_ptr禁用了拷贝构造函数
std::unique_ptr<Object> pobjc;
pobjc = pobja;//F,unique_ptr禁用了=运算符重载函数
std::unique_ptr<Object> pt(std::move(pobja))//使用拷贝移动构造实现构造-拷贝移动构造,实现资源的转移
std::unique_ptr<Object> pt2;
pt2 = std::move(pobja);//T,底层上将pobja强转为std::unique_ptr<Object>&& 类型
return 0;
}
1.3.2对单个对象与数组指针delete冲突问题的解决
unique_ptr通过辅助类型Deletor的帮助达到对该问题的解决,见下例
//自建辅助类型Deletor对普通类型的接受方案
template<class _Ty>
class MyDeletor
{
public:
myDeletor(){};
void operator()(_Ty* ptr) const
{
if (ptr != nullptr)
{
delete ptr;
}
}
};
//对Deletor部分特化,使其能够接收数组类型
template<class _Ty>
class MyDeletor<_Ty[]>
{
public:
myDeletor() = default ; //c11标准。意为添加一个默认函数,等效于 myDeletor(){};
void operator()(_Ty* ptr) const
{
if (ptr != nullptr)
{
delete[]ptr;
}
}
};
int main()
{
MyDeletor<Object> op;
MyDeletor<Object[]> arop;
//通过自建的辅助类型Deletor达到对不同数据类型的delete
Object *p = new 0bject(10);
op(p);
p = new Object[10];
arop(p) ;
}
了解了Deletor的用法,再来重新编写my_unique_ptr
//额外传入 _Dx来接收MyDeletor,使得my_unique_ptr能通过MyDeletor来delete不同数据类型
//下面的函数模板用来处理常规数据类型
template<class _Ty,class _Dx = MyDeletor<_Ty>>
class my_unique_ptr
{
public:
//using是c11的新用法,等价于typedef
//如using pointer = _Ty*等价于typedef _Ty* pointer
using pointer = _Ty*;
using element_type = _Ty;
using delete_type = _Dx;
private:
_Ty* _Ptr;
_Dx _mydeletor;//创建_Dx类型的实体_mydeletor
public:
my_unique_ptr(pointer * p = NULL):_Ptr(p){}
~my_unique_ptr()
{
if( _Ptr !=NULL)
{
_mydeletor(_Ptr);
_Ptr = NULL;
}
}
my_unique_ptr(const my_unique_ptr&) = delete; //禁用拷贝构造
my_unique_ptr& operator=(const my_unique_ptr&) = delete;//禁用了赋值语句重载
//获取deletor
_Dx& get_deletor() const
{ return _mydeletor;}
const _Dx& get_deletor() const
{ return _mydeletor;}
_Ty* get()const
{ return _Ptr;}
_Ty& operator* () const
{ return*_Ptr; }
_Ty* operator->() const
{ return _Ptr; }
operator bool()
{ return _Ptr != nullptr }
//release函数,使指针不再指向原空间,返回原空间地址
_Ty* release()
{
_Ty* old = _Ptr;
_Ptr = nullptr;
rerturn old;
}
//重置函数,重新定向_Ptr的指向,并将其旧的所拥有的资源释放
void reset( _Ty* _P = nullptr)
{
pointer old = _Ptr;
_Ptr = _P;
if(old != nullptr)
_mydeletor(old);
}
void swap(my_unique_ptr _Y)
{
std::swap(_Ptr,_y._Ptr);
std::swap(_mydeletor._Y._mydeletor);
}
my_auto_ptr& operalor=(const my_auto_ptr& _Y)
{
if (this == &_Y) return *this;
if (_Owns)
{ delete _Ptr; }
_Owns = _Y._Owns;
_Ptr = _Y.release() ;
return 0;
}
//移动构造
my_unique_ptr(my_unique_ptr&&_Y)
{
Ptr = _Y._Ptr;
_Y._Ptr = nullptr;
}
//移动赋值
my_unique_ptr& operator=(my_unique_ptr&&_Y)
{
if(this != &_Y)
reset(_Y.releas());
return *this;
}
};
//下面的函数模板用来处理数组数据类型
template<class _Ty,class _Dx>
class my_unique_ptr<_Ty[]._Dx>
{
···;//···表示其余各部分与上例相同
//额外增加一个对数组下标访问的重载
_Ty& operator[](size_t Idx) const
{
return _Ptr[_Idx];
}
};
int main()
{
std::unique_ptr<Objcect> pobja (new Object(10));
std::unique_ptr<Object[]> pobjb(new Object[10]);
make_unique
int main {
std::unique_ptr<Object> op(new Object(10));
std::unique_ptr<Object> op2 = std::make_unique<Object>(100);//两种构建方式等价
;
return 0;
}
1.4shared_ptr(共享型智能指针)的使用&线程安全
use_count返回引用计数
int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;
std::shared_ptr<Object> sp2 = std::make_shared<Object>(20);
std::shared_ptr<Object> sp3;
cout<<sp1.use_count()<<endl; //1
cout<<sp2.use_count()<<endl; //1
cout<<sp3.use_count()<<endl; //0
}
int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;
std::shared_ptr<Object> sp2 = std::make_shared<Object>(20);
std::shared_ptr<Object> sp3;
sp3 = sp1;//如果sp3以sp1为参数进行拷贝构造初始化其计数变化与下例相同
cout<<sp1.use_count()<<endl; //2
cout<<sp2.use_count()<<endl; //1
cout<<sp3.use_count()<<endl; //2
}
operator bool 判断指针是否有指向对象
实现如下operatorbool () const { return ptr != nullptr; }
int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;
std::shared_ptr<Object> sp2;
if(sp1); //1
if(sp2); //0
return 0;
}
reset 替换指向对象
void reset( _Ty* p = nullptr)
{
if (this->ptr != nullptr & --this->ptr->ref == 0)
mDclctor(ptr);
ptr = new RefCnt<_ Ty>(p);
}
线程安全
在多线程中,使用atomic 原子操作来防止+,-等非原子操作所引发的异常
include<atomic>
是包含原子操作的库
weak_ptr的引入
如图,在上例中,因实例之间的循环套用,导致其生命周期结束时,计数器仍未减至0
1.5weak_ptr
weak_ptr 作为弱引用指针不具备对对象的引用(该功能由强引用指针实现),其仅指向引用计数器 RefCnt,并且以此来解决环形引用的问题
对循环问题的解决