栈对象:仅在其定义的程序块运行时才存在;
static对象:在使用前创建,程序结束时销毁;
堆对象:动态分配的对象,在程序运行过程中可以随时创建或销毁。
C++通过new和delete管理动态内存:
new为对象分配空间并返回一个指向该对象的指针。
delete接受一个对象的指针,销毁该对象,释放与之关联的内存。 delete p; // p是一个指针。delete p后,只是释放了p所指向的内存空间,但指针p仍存在,应在delete之后置p为nullptr,否则会导致p指向一个未定义的内存空间。
动态分配的对象,在主动释放之前会一直存在。对动态分配的对象:若不及时释放对象易造成内存泄漏,若释放对象后仍有指针指向该对象(即产生了空悬指针),会导致使用非有效内存而出错;(内存泄漏 memory leak:指内存被申请后,无法被释放,导致内存一直被占用的问题)
因此,C++提供两种智能指针以方便管理动态对象,定义在头文件<memory>中:
1、shared_ptr: 允许多个指针指向同一个对象
2、unique_ptr: 独占所指对象
其他智能指针weak_ptr: 伴随类,指向shared_ptr所管理的对象。
shared_ptr\ unique_ptr 智能指针会记录共享对象的指针数(包括它自己),当共享对象的指针数为0时,回收该对象。
申请一个动态内存的对象:
使用new来创建一个动态内存上的对象,这个对象应该用delete来释放内存以及析构。
new的使用方式:new [类型名称] (构造对象的参数列表); // 省略参数列表表示对象默认初始化。new返回一个指针,该指针指向new创建的对象。
-
默认初始化:
int *pi = new int; // pi指向一个未命名的对象,其被默认初始化,但类型为内置类型,所以该对象未被初始化
string *ps = new string; // ps指向一个默认初始化为空串的string对象
-
直接初始化:
int *pi = new int(); // 使用无参构造函数初始化pi指向的未命名对象,该对象值初始化为0;
string *ps = new string();
-
auto初始化器判断要初始化的对象类型(只支持一个参数):
auto *pi = new auto(obj);
auto *p = new auto{a, b, c}; // 错误,只能有单个初始化器
-
用new分配const对象是合法的:
const int *p = new const int(1024);
内存耗尽处理:当执行int *pi = new int; 内存不足时,new抛出std::bad alloc异常,因此为防止抛出异常,将语句改为int *pi = new (nothrow) int; 使new不抛出异常,pi初始化为一个空指针
智能指针通用操作:
智能指针为模板类,默认初始化为空指针。
声明智能指针:shared_ptr<string> sp; unique<int> up;
使用智能指针:*p;
智能指针支持的操作:
-
p //将智能指针用作判断条件,空指针为false
-
*p // 得到智能指针所指对象
-
p->mem // 访问所指对象的成员
-
p.get() // 返回p中所保存的内置指针。
-
swap(p, q) ; p.swap(q); // 交换p、q中所保存的指针
delete:
int i; int *p = &i; int *p2 = new int(42); int *p3 = p2;int *pn = nullptr;
delete i; // 不被允许的操作:i不是动态内存的对象
delete p; // 不被允许的操作,p指向的不是动态内存的对象
delete p2; // 正确,p2指向的是动态内存,回收p2及其所指向的对象的空间,此时p3变为空悬指针
delete p3; // 错误,p3指向的空间已在delete p2时被回收,delete p3是未定义的行为
delete pn; // 正确,回收一个空指针不会出现错误
一、可共享的智能指针shared_ptr<T>
只有shared_ptr指针支持的其他操作:
-
make_shared<T>(args); // 返回一个指向动态分配的T类型对象的shared_ptr指针,使用args的内容构造该对象, 类似于make_pair<first, second>(key,value);
-
p.use_count(); // 返回当前指向p所指对象的指针数量(包括p),执行速度较慢,一般用于调试
-
p.unique(); // 是否独占对象,独占则返回true,若有其他智能指针也指向该对象返回false。
-
shared_ptr<T> p(q); // 将p所指内容拷贝给q,智能指针p的共享指针数(对象引用计数)cnt++
-
p = q; // 将q所指内容覆盖p,智能指针q共享指针数cnt++,但p原指向对象cnt--;
智能指针会记录共享对象的指针数(包括它自己),当共享对象的指针数为0时,回收该对象。
shared_ptr基础操作:
shared_ptr<int> sp(new int(42)); // 用new返回的普通指针初始化智能指针sp
shared_ptr<int> sp(sq); // 拷贝初始化sp,使sp与sq共享同一个对象
shared_ptr<int> sp(uq); // 用unique_ptr初始化shared_ptr,使sp指向uq的对象,置up为空。
shared_ptr<int> sp(q, d); // 用内置指针q初始化shared_ptr,使用可调用对象d代替delete
shared_ptr<int> sp(sq, d); // shared_ptr类型的sq初始化sp,且使用d操作代替delete
sp.reset(); // sp放弃所指对象的控制权;
* 若对象还有其他shared_ptr管理,不释放对象,并将其他对象的引用计数减一,若放弃后计数清零,则释放对象
sp.reset(q); /* 先使sp的引用计数-1,减后若计数为0,回收所指对象内存;
* 不为0,则还有其他指针共享对象,sp计数清零但不回收内存,最后将内置指针q指向的对象给sp管理 */
sp.reset(q, d); // 执行reset(q)并用可调用对象d替换delete
智能指针的构造函数是explicit的,不能执行隐式转换,所以不可通过赋值将内置指针和unique_ptr转换为shared_ptr,也不能将内置指针和unique_ptr作shared_ptr的形参对应的实参
智能指针和内置指针混用的风险:
void process(shared_ptr<int> sp){
// statement
} // sp离开作用域,被销毁
int *x(new int(42));
process(x); // 不被允许的操作,内置指针不能隐式转换为shared_ptr<int>类型
/* 以下操作,用x初始化一个shared_ptr<int>的未命名对象,使sp与该临时对象共享x所指向的对象;
当process函数结束后,未命名对象与sp都离开作用域,被销毁。
此时它们的引用计数置为0,将会释放x所指向对象,但x不在该作用域,x未被销毁,成为空悬指针(指向一个未定义的内存空间)。 */
process(shared_ptr<int>(x));
int a = *x; // 错误,x指向的对象未定义。
因此,不要随意将一个内置指针转为智能指针,可能导致内置指针成为空悬指针的情况。
不要用get初始化另一个智能指针:
int *q = sp.get();
// 程序块
{
/* sp2引用计数为1,sp引用计数也为1,sp2、sp并不知道互相的存在。
因此当sp2被销毁,sp所指对象也被销毁,sp、q都成为空悬指针。
同样,当sp计数为0时,sp2也成为 空悬指针。*/
shared_ptr<int> sp2(q);
}// sp2被销毁,计数清零,所指内存被释放
int a = *sp, b = *q; // 错误,sp、q所指对象已经被释放。
以上例子说明,将内置指针转为一个智能指针,即使只是创建了一个临时的智能指针,也会导致所指对象内存被释放,变为空悬指针。
使用智能指针的好处:当程序异常退出时,内存可以正确释放:
void f(){
shared_ptr<int> sp(new int(1024));
// 异常退出,1024所在的空间被释放
}
void f(){
int *p = new int(1024);
// 异常退出,delete a未执行,1024所在空间造成内存泄漏
delete a;
}
使用智能指针技术管理不具有较好的析构方式的类:
struct Destinction; // 连接目的地类型
struct Connection; // 连接类型
Connection connect(Destinction*); // 打开连接的函数
void end_connect(Connection*); // 关闭连接
void f(Destinction &d){
Connection c = new connect(d);
shared_ptr<connection> net_sp(&c, end_connect);
// 执行连接后的操作
// 执行完毕,net_sp被销毁,并执行end_connect操作
}
为更好地使用智能指针,应遵循以下原则:
-
不使用同一内置指针值初始化多个智能指针;
-
不delete由get()获取的指针;
-
不使用get()初始化或reset另一个指针;
-
若必须使用get获取的指针,则记住当最后一个shared_ptr被释放后,你的指针就无法使用了;
-
若使用智能指针管理不能被delete释放的资源,记得定制删除器;
二、独占的智能指针:unique_ptr<T>
初始化:
unique_ptr<int> up; // 保存空指针的up
unique_ptr<int> up(new int(1024)); // up拥有值为1024动态分配对象
以下操作不被允许:
unique_ptr<int> up1(up2); // unique_ptr 不允许用另一个智能指针初始化(包括shared_ptr\unique_ptr类型)
up1 = up2; // unique_ptr不支持拷贝
unique_ptr支持的操作:
unique_ptr<T> up;
unique_ptr<T, D> up(new T(value), d); // d是一个可调用对象,D是可调用对象的类型,d替换了up的delete操作。
unique_ptr<T, D> up(d); // up为空,删除操作为d
up.reset(); // 释放up所指内存空间,并将up置为空指针
up.reset(q); // 释放up所指对象空间,并使up指向q所指对象
up.release(); // up释放对对象的控制权,返回所保存的指针,并将up置为空
up.reset(nullptr); // 释放up所指对象空间并使up为空
up = nullptr; // 释放up对象空间,并置up为空
up.reset()、up.release() 与 sp.reset()的区别:
up的reset会将up所指对象释放并置up为空,sp的reset不会释放所指对象只会减少引用计数,up.release()不会释放所指对象,但会使up置空并返回所保存的指针。