C++primer -智能指针

栈对象:仅在其定义的程序块运行时才存在;

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置空并返回所保存的指针。

  • 54
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值