1. 智能指针 与 动态内存
2. 智能指针
C++11中有两种智能指针,用于管理动态对象,可以自动释放所指向的对象
shared_ptr允许多个指针指向同一个对象weak_ptr是伴随类,弱引用,指向shared_ptr指向的对象
unique_ptr则“独占”所指向对象
2.1. shared_ptr类
允许多个指针指向同一个对象,默认初始化的智能指针包含一个空指针。
shared_ptr的独有操作包括
make_shared<T>(args); //返回一个shared_ptr,指向一个动态分配的类型T对象,使用args初始化
shared_ptr<T>p(q); //p是shared_ptr q的拷贝:递增q的计数器;q的指针必须能转换为T*
p = q; //两者都是shared_ptr,所保存指针必须能互相转换。递减p原来指向对象的引用计数,递增q的引用对象。(若p原指向对象计数变为0,则自动销毁)
p.unique(); //若使用p.use_count()为1,则返回true,否则false
p.use_count();
安全的分配方法是调用make_shared<T>,在动态内存中分配对象并初始化,返回指向这个对象的shared_ptr。
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<int> p2 = make_shared<int>();
shared_ptr<string> ps = make_shared<string>(10,'9');
也可以结合new进行shared_ptr的初始化,需要注意的是接受指针参数的智能指针构造函数是explicit的,不能隐式转换,必须直接初始化形式。
shared_ptr<T> p(q); //p管理内置指针q指向对象,且q必须指向new分配内存且类型能转换为T*
shared_ptr<T> p(u); //从unique_ptr u接管对象所有权,将u置为空
shared_ptr<T> p(q,d); //内置指针q,d为可调用对象用于代替delete
shared_ptr<T> p(p2,d); //p是shared_ptr p2的拷贝,区别在于p调用d来delete
p.reset();
p.reset(q);
p.reset(q,d);
//例
shared_ptr<int> p1 = new int(1024); //错误
shared_ptr<int> p2(new int(1024)); //正确
初始化的指针必须指向动态内存,因为默认使用delete释放关联的对象(也可以自己提供操作代替delete)。
shared_ptr有一个计数器,称之为引用计数reference count。
shared_ptr的析构函数,递减引用对象的计数,若计数减为0则销毁指向对象并释放内存【delete? 依实现而定可能】:
不delete get()返回的指针,不要将get()用于初始化另一个智能指针,因为这样两个智能指针没有关联,会引发意外的delete(新智能指针销毁时隐式delete)。
智能指针与异常:发生异常时,程序块过早结束,也能正确回收智能指针所指向的内存(因为局部对象智能指针会被销毁)。
删除器:默认情况下shared_ptr<T>指向动态内存,可以指定函数(删除器,deleter)代替delete。删除器函数必须能完成对 T*进行释放的操作。
**shared_ptr默认删除器为delete,对于动态数组(new [])无法正常调用delete[],需要手动指定删除器。【可能由于】
**此外,shared_ptr不支持对动态数组的下标访问。
循环引用问题:https://blog.csdn.net/Jacketinsysu/article/details/53341370
2.2. unique_ptr类
“独占”所指向对象,指针被销毁时对象也被销毁。
定义时,没有类似make_shared<T>,必须使用直接初始化形式:
unique_ptr<double> p1; //可指向一个double的unqiue_ptr
unqiue_ptr<int> p2(new int(42)); //指向值为42的int
不支持拷贝与赋值,但可以通过reset()和release()将指针所有权从一个(非const)的unqiue_ptr转移到另一个
unqiue_ptr<string> p1("new string");
unqiue_ptr<string> p2(p1); //错误,不支持拷贝
unqiue_ptr<string> p3;
p3 = p1; //错误,不支持赋值
//
unqiue_ptr<string> p2(p1.release()); //release将p1置空
unqiue_ptr<string> p3(new string("Trex"));
p2.reset(p3.release()); //reset释放p2原来指向的内存
release()返回保存的指针并将unqiue_ptr置空。
reset()接受一个可选参数,将unqiue_ptr指向给定的指针,如果原先unqiue_ptr不为空,则先释放原对象。可以直接调用u.reset()则直接释放u的对象并置空u。
特殊:可以拷贝或赋值一个将要被销毁的unqiue_ptr
实际上是使用了移动语义(移动拷贝)。
例:一个函数返回一个unqiue_ptr
unqiue_ptr<int> clone(int p){
return unqiue_ptr<int> (new int(p)); //正确
}
unqiue_ptr<int> clone_2(int p){
unqiue_ptr<int> ret(new int(p));
return ret; //正确
}
删除器
与shared_ptr不同,unqiue_ptr的删除器是类型的一部分,重载删除器会影响到unqiue_ptr类型以及如何构造该对象的类型。必须在尖括号中指定删除器的类型。
unqiue_ptr<objT, delT> p (new objT, fcn);
原因在于,删除器是unqiue_ptr的一部分,删除器成员的类型在编译时绑定,保存在对象中。实际上删除时执行如下代码
//unqiue_ptr的del在编译时绑定;直接调用实例化的删除器
del(p);
与此相比,shared_ptr的删除器并不是类型一部分,是运行时绑定的,可以随时改变删除器的类型(使用reset)。但是类成员的类型在运行时无法改变,不能直接保存删除器在类中。删除器是间接保存的,通过一个成员del来访问。【根本原因是 shared_ptr并不独占指针,考虑shared_ptr动态多态的情况,很可能需要指定删除器】。删除时代码如下:
//del的值在运行时才知道
del? del(p) : delete p; //del(p)运行需要转跳到del的地址
2.3. weak_ptr类
不控制所指向对象生存周期的智能指针,指向一个由shared_ptr管理的对象。绑定到一个shared_ptr不会增加其计数,一旦最后一个shared_ptr被销毁对象也会被销毁。
weak_ptr<T> w; //空的weak_ptr<T>可以指向类型为T的对象
weak_ptr<T> w(sp); //与shared_ptr指向相同对象的weak_ptr,T必须能转换为sp指向的对象
w = p; //p可以为weak_ptr或者shared_ptr,赋值后共享对象
w.reset(); //置空w
w.use_count(); //与w共享对象的share_ptr的数量
w.expired(); //若w.use_count()为0,返回true,否则false
w.lock(); //如果expired()为true,则返回空的shared_ptr,否则返回一个指向w对象的shared_ptr
创建时要用shared_ptr来初始化:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
由于对象可能不存在,不能使用weak_ptr直接访问对象,必须调用lock(),此函数检查weak_ptr指向的对象是否存在。存在则返回一个指向共享对象的shared_ptr。
if(shared_ptr<int> np = wp.lock()){//若np不为空则成立
//在if中,np与wp共享对象
}
3. 动态内存
3.1. new
new的主要功能:
- 申请内存(operator new, 或者 operator new[])
- 调用对象构造函数
- 返回指向对象的指针
默认情况下,new动态分配的对象是默认初始化的(内置类型或组合类型的值未定义)类类型则用默认构造函数初始化。
可以用直接初始化方式、传统构造方式(圆括号)、列表初始化(花括号)、也可以进行值初始化
int *pi = new int(1024); //直接初始化
string *ps = new string(10, '9'); //传统构造
vector<int> *pv = new vector<int>{0,1,2,3,4}; //列表初始化
string *ps1 = new string; //默认初始化
string *ps2 = new string(); //值初始化,对类类型等价于默认初始化,调用默认构造函数
int *pi1 = new int; //默认初始化,*pi1的值未定义
int *pi2 = new int(); //值初始化,*pi2的值为0。对内置类型,值初始化有良好的定义值。
动态分配const对象
可以动态分配const对象,但必须初始化
const int *pci = new const int(1024); //初始值为1024
const string *pcs = new const string; //空字符串
异常情况
若new不能分配所要求的的空间,就会抛出一个bad_alloc异常。
可以指定不抛出错误:(向new传递额外参数的定位new,placement new)
int *p2 = new (nothrow) int;
3.2. delete
- 调用析构函数,销毁对象
- 回收内存(operator delete, 或operator delete[])
delete可以对空指针安全操作。需要注意的是,delete之后应当重置指针。
**可以合法delete空指针
**编译器无法分辨指针指向静态、动态对象,以及动态指针指向的内存是否已经释放。因此,释放静态对象地址、释放两次同一个指针,是未定义的
3.3. 动态数组
3.3.1. 使用new T[]
new T[]可以分配“动态数组”,但实际上并不得到数组类型,而是一个数组元素类型的指针。因此,无法使用begin和end函数,此外用sizeof也无法获得维度信息,也不能用范围for(数组类型包含维度信息)。
初始化动态分配对象的数组
int *pia1 = new int[10]; //默认初始化
int *pia2 = new int[10](); //值初始化
int *pia3 = new int[10]{0,1,2,3}; //前三个使用初始化器,后面元素使用值初始化
异常情况
失败则抛出bad_array_new_length异常,与new相似
分配空数组合法
返回指针不与其他指针相同的,就如尾后指针。
3.3.2. delete[]
delete p; //p必须指向一个动态分配的对象或为空
delete[] pa; //pa必须指向一个动态分配的数组或为空
delete []完成的工作:
- 销毁数组中的元素(逆序销毁,最后一个先被销毁,第一个最后销毁)
- 释放内存
**释放指向数组的指针时,必须使用[]:指示编译器指针指向对象数组的第一个元素。若只用了delete则行为是未定义的。
3.3.3. 智能指针与动态数组
标准库提供了一个可以管理new分配的数组的unique_ptr版本。用法:
unique_ptr<T[]> up(new T[10]);
up.release(); //自动调用delete[]销毁指针
与普通的unique_ptr略有不同,支持下标访问,不支持点和箭头的成员运算符。
shared_ptr不直接支持管理动态数组,需要自己定义删除器。也不支持下表访问。
3.4. allocator类
allocator是一个模版,将内存分配与对象构造分离。可以分配大块未构造内存(unconstructed),真正需要时才执行对象构造。
allocator<string> alloc; //
auto const p = alloc.allocate(n); //分配n个未初始化的string
3.4.1. 构造对象
a.construct(p, args); //p必须为一个类型为T*的指针,指向原始内存;args为传递给T类型构造函数的参数,用于构造对象
auto q = p ; //q指向最后构造的元素之后的位置(用于构造)
alloc.construct(q++);
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");
使用未构造内存,行为未定义
3.4.2. 销毁对象与释放内存
while(q!=p)
alloc.destory(--q);
alloc.deallocate(p,n); //p不能为空
3.4.3. 拷贝/填充未初始化内存
uninitialized_copy(b,e,b2); //从b开始到e,范围内的元素拷贝到b2开始的内存中
uninitialized_copy_n(b,n,b2); //从b开始的n个元素,拷贝到b2开始的内存
uninitialized_fill(b,e,t); //从b到e到原始内存中,创建对象,值为t的拷贝
uninitialized_fill_t(b,n,t);
返回递增后的目标位置迭代器。