C++智能指针


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销毁前内存都不会释放,所以不用之后需要销毁。比如容器中存放智能指针,容器重排后智能指针不再使用,但是指针指向的对象仍然被引用。

使用动态内存原因:

  1. 程序不知道自己需要使用多少对象。
  2. 程序不知道对象的准确类型。
  3. 程序需要在多个对象间共享数据。

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写一个伴随指针类,该类不会影响被伴随类资源的生存周期,但是可以检查被伴随类的资源从而阻止访问部分资源。

上一篇:【C语言】PAT乙级1004 成绩排名 :读入 n(>0)名学生的姓名、学号、成绩,分别输出成绩最高和成绩最低学生的姓名和学号。


下一篇:C++11——共享智能指针