typora-copy-images-to: ./
智能指针知识总结:
来源cpp,自己总结复习用。
对象与内存
内存分布:
- 静态内存:局部static对象,类static数据成员,函数之外定义的变量。
- 栈内存:定义在函数内的非static对象。
- 内存池(*空间,堆):动态分配对象——运行时分配的对象。
自动和static对象的内存管理:
-
全局对象,程序启动时分配内存,程序结束时释放内存。
-
局部自动对象,进入定义的程序块后分配内存,离开块之后释放内存。
-
局部static对象,首次使用前分配内存,程序结束时释放内存。
动态分配对象的内存管理:
-
生存期与在哪里创建无关,只有显式释放后对象才被销毁。
-
直接管理内存运算符new,动态内存中分配空间并返回一个指向该对象的指针。
-
直接管理内存运算符delete,接受一个指向动态对象的指针,销毁对象并释放关联内存。
-
问题1:忘记释放内存,会造成内存泄露。
-
问题2:指针引用内存下释放内存,会引用非法内存的指针。
智能指针:用于更安全地使用动态对象,因为动态分配对象的正确释放很难掌控,标准库就定义了两种智能指针类型,帮助我们自动释放一个对象。智能指针定义在中,使用方法同模板。
直接管理内存new和delete的使用:
new
堆中分配的内存是无名的,new无法为分配对象命名,因此new会返回一个指向对象的指针。动态分配的对象是默认初始化的。
int *ip = new int;//内置类型,默认初始化为未定义。
int *ip2 = new int();//内置类型,值初始化为0。
//同string类似的类类型,其有默认构造,不论哪种形式都会初始化。
string *s = new string;
string *s2 = new string();
//用auto初始化器推断分配类型,只能提供单一初始化器。
auto p = new auto(obj);
若堆内存耗尽,使用new会抛出一个类型为std::bad_alloc的异常。我们使用定位new(placement new)阻止。它们都存在于new头文件中。
int *p = new (nothrow) int ;//分配失败会返回一个空指针。
delete
传递给delete的指针,要么是指向动态分配的内存,要么为空。注意:编译器不能分辨指针指向静态还是动态分配对象,同时也不知道指向内存是否已经释放。
内置指针类型管理内存:
- 需要调用者或者函数本身释放该内存,否则会发生内存泄漏。
- 我们最好在delete操作后,将指针置为空来检测错误。
- 同一块内存被先后释放两次,堆会被破坏。
关于第二点,因为delete后指针仍然指向已释放的动态内存的地址,此时指针为空悬指针。我们不能继续使用该指针,因此需要置为空而不绑定到任何对象。同时,delete只对当前指针有效,而对其他指向同一对象的指针无效。
shared_ptr:
管理方式:允许多个指针指向同一个对象。
安全分配和使用动态内存的函数:make_shared,该函数返回一个shared_ptr。
//创建一个智能指针
shared_ptr<T> pointer; //执行默认初始化保存一个空指针
//使用make_shared创建一个智能指针,传递参数必须要匹配对象的构造函数
shared_ptr<T> pointer2 = make_shared<T>(); //执行值初始化
shared_ptr<int> pointer3 = make_shared<int>(2); //该智能指针指向一个值为2的int对象
拷贝或赋值操作,每个shared_ptr有个关联的计数器(引用次数),会记录有多少个其它shared_ptr指向相同的对象。
shared_ptr<int> p = make_shared<int>(2); //对象引用计数1.
auto r = p; //赋值操作,对象引用计数+1,结果为2.
auto q = make_shared<int>(3); //指向值为3的int对象的引用次数1.
q = r ; // 没有指向值为3的int对象,引用次数0,自动释放管理对象。
如何自动销毁管理对象:通过析构函数,该函数会销毁、释放对象所分配的资源。shared_ptr每次执行析构函数,都会递减以此引用次数,所以当引用次数为0时,就会销毁对象,释放内存。
自动释放关联内存:shared_ptr销毁前内存都不会释放,所以不用之后需要销毁。比如容器中存放智能指针,容器重排后智能指针不再使用,但是指针指向的对象仍然被引用。
使用动态内存原因:
- 程序不知道自己需要使用多少对象。
- 程序不知道对象的准确类型。
- 程序需要在多个对象间共享数据。
shared_ptr与new的结合使用:
//使用new返回的指针初始化智能指针,但必须为显式初始化,因为内置指针不能隐式转换为智能指针。
shared_ptr<T> p(new int(1024)); //显式转换
//常见错误,shared_ptr临时对象使用内置指针构造。
int *i(new int(1024));
void process(shared_ptr<int> (i));
//process块结束后,智能指针自动析构,i成为了悬浮指针。
//智能指针的get函数可以返回一个内置指针,考虑到上述错误,我们不应该用该指针初始化或赋值智能指针,也不应该delete该内置指针。
shared_ptr<int> p2(new int(512));
int *p3 = p2.get();
//reset操作,可以使智能指针指向新对象。
函数退出有两种可能:正常处理结束或异常退出。使用内置指针管理内存时,若在new和delete之间发生了异常,由于异常退出,不会执行delete操作,因此内存不会释放。
有些基于C和C++的类,没有良好的析构函数,需要我们自己进行释放操作。使用智能指针管理的内容不是new分配的内存,则需要根据该类传递一个删除器。
shared_ptr<T> P(q,deleter) //p接管q指向的内容,deleter为可调用对象(删除器)
unique_ptr:
管理方式:独占所指动态对象,需要绑定到new返回的指针上。当其被销毁时指向对象也被销毁,与shared_ptr不同,例如拷贝的shared_ptr,其被销毁只使引用次数递减。
初始化方式:与shared_ptr类似,我们要显示初始化。但又有不同,因为unique_ptr的管理方式,我们不能使用拷贝和赋值操作。
unique_ptr<int> p(new int(1024));
也可以通过release释放操作/reset重定操作,绑定到另一个unique_ptr上。
unique_ptr<int> p2(p.release()); //release返回p指向的对象的指针
unique_ptr<int> p3(new int(512);
p2.reset(p3.release()); //reset操作将释放其原指向的对象
/*
p2.release(); //单独的释放指针控制权,不能对资源进行释放。 */
delete p2; //需要使用直接管理内存的方式显式删除unique_ptr
unique_ptr可以拷贝或赋值一个即将被销毁的unique_ptr,如可从函数中返回一个unique_ptr.
重载unique_ptr的默认删除器:
unique_ptr<objT,delT> p (new objT,fcn);//delT对象用于释放objT对象,fcn为delT类型的对象
weak_ptr:
管理方式:伴随类&弱引用,指向shared_ptr所管理的对象,但不控制对象的生存期,所以其创建不会对shared_ptr引用次数有影响,进一步地说,其指向的对象还存不存在都不确定。
初始化方式:使用一个shared_ptr初始化。或者隐式初始化为空。
shared_ptr<int> p(new int(1024));
weak_ptr<int> p2(p);
由于其管理方式,我们需要确认unique_ptr其指向对象是否存在,使用lock来检查。
if(shared_ptr<int> p = p2.lock()){
//使用p安全访问对象
}
有了lock,我们可以使用weak_ptr写一个伴随指针类,该类不会影响被伴随类资源的生存周期,但是可以检查被伴随类的资源从而阻止访问部分资源。