智能指针shared_ptr与unique_ptr详解

为什么使用动态内存

程序不知道自己需要多少对象;
程序不知道对象的准确类型;
程序需要在多个对象之间共享数据;

动态内存在哪里

程序有静态内存、栈内存。静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建或销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式的销毁它们。(c++ primer P400)

自由存储区和堆

自由存储是c++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区
堆是操作系统维护的一块内存
虽然c++编译器默认使用堆来实现自由存储。但两者不能等价

动态内存与智能指针

我们知道c++需要注意的地方之一就是对内存的管理,动态内存的使用经常会出现内存泄漏,或者产生引用非法内存的指针
新的标准库提供了两种智能指针类型来管理动态对象:
(1)shared_ptr 允许多个指针指向同一个对象
(2)unique_ptr 独占所指向的对象
定义在memory头文件中,他们的作用在于会自动释放所指向的对象


智能指针的本质

智能指针的实质是一个对象,行为却表现的像一个指针


unique_ptr的“独占”?

先说说为什么shared_ptr允许多个指针指向同一对象吧
因为 动态对象的所有权不确定。对象可以在多个作用域中共享,又不能像栈对象一样自由地值拷贝。只要有一个对象\作用域还持有这个动态对象,他就不能销毁,当他没有用时,自动销毁,这个机制后面再讲。
unique_ptr的“独占”是指:不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。例如:

std::unique_ptr<int> p (new int);
std::unique_ptr<int> q = p; //error

但是unique_ptr允许通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,注意,这时它本身就不再拥有原来指针的所有权了。

std::unique_ptr<int> p (new int);
std::unique_ptr<int> q = std::move(p); //ok

shared_ptr基操

shared_ptr也是一个模板,所以我们在创建的时候,需要提供指针指向的类型。

shared_ptr<string> p1;  //指向string
shared_ptr<list<int>> p2;   //指向int的list

默认初始化的智能指针为一个空指针,智能指针的使用类似于普通指针:

if (p1 &&p1->empty()) //如果p1不为空且p1指向一个空string 
{
    *p1 = "lvbai";  //赋值
}
  • shared_ptr和unique_ptr都支持的一些操作
    shared_ptr<T> p1; //空智能指针,可以指向类型为T的对象
    unique_ptr<T> p2;

    if (p1) //将p1用作一个条件判断,若p指向一个对象,为true 
    {...}

    *p1;    //解引用p1,获得他指向的对象

    p->member;  //等价于*(p1).member

    p1.get();   //返回p1中保存的指针。若智能指针释放了对象,则这个指针指向的对象也消失了 

    swap(p1, q);     //交换p和q中的指针即p1.swap(q);
  • shared_ptr独有的操作
    make_shared<T> (args) //返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化

    shared_ptr<T> p(q)  //p是shared_ptr q的拷贝;此操作会递增q中的计数器,q中的指针必须能转换为T*

    p = q //p q都是shared_ptr,所保存的指针必须都能相互转换,此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放

    p.unique()  //若p.use_count()为1,返回true

    p.use_count()   //返回与p共享对象的智能指针的数量,可能很慢,主要用于调试

make_shared函数

什么是make_shared函数,他的作用是什么呢?
make_shared函数是一个安全的分配和使用动态内存的方法。他会在动态内存中分配一个对象并初始化它,返回值是一个指定类型的shared_ptr,同样也定义在memory的头中

    //指向一个值为42的int的shared_ptr
    shared_ptr<int> p = make_shared<int> (42);
    //指向一个值为"999"的string
    shared_ptr<string> q = make_shared<string> (3, '9');
    //指向一个值初始化的int,即值为0
    shared_ptr<int> w = make_shared<int> ();
  • make_shared怎么初始化
    make_shared用其参数来构造给定类型的对象,也就是说,我们的参数,必须符合给定类型的某一构造函数,不传参就默认初始化

  • 与shared_ptr的区别,为什么使用make_shared更好
    他相比shared_ptr减少了内存分配的次数,而内存分配的代价较高;他会立即获得申请的裸指针,不会造成内存泄漏

  • make_shared的缺点
    当构造函数是保护或私有的时,无法使用make_shared;对象调额内存可能无法及时回收

shared_pt的强引用、弱引用

关于智能指针怎么实现自动释放对象,简单来说,智能指针的内部有一个引用计数,当有指针指向其对象,引用计数就会++,相反–,当减为0时,自动释放该对象

到底是用一个计数器还是其他数据结构来记录有多少指针共享对象,完全由标准库的具体实现来决定。关键是智能指针类能记录有多少个shared_ptr指向相同的对象,并能在恰当的时候自动释放

强引用和弱引用就是shared_ptr用来维护引用计数的信息

  • 强引用
    用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).
  • 弱引用
    用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).

  • 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象

  • 当指向一个对象的最后一个shared_ptr被销毁,shared_ptr类会自通过调用对应的析构函数销毁此对象  

  • shared_ptr会自动释放相关联的内存

//该函数返回一个T类型的动态分配的对象,对象是通过一个类型为Q的参数来进行初始化的
shared_ptr<T> Fun(Q arg) 
{
    //对arg进行处理
    //shared_ptr负责释放内存
    return make_shared<T>(arg);
    //由于返回的是shared_ptr,我们可以保证他分配的对象会在恰当的时候释放
}
void use_Fun(Q arg) 
{
    shared_ptr<T> p = Fun(arg);
    //使用p
}
//函数结束,p离开了作用域,他指向的内存被自动释放
shared_ptr<T> use_Fun(Q arg) 
{
    shared_ptr<T> p = Fun(arg);
    //使用p
    return p; //返回p时,引用计数递增
}
//p离开了作用域,但他不会释放指向的内存

shared_ptr和new结合使用

    shared_ptr<int> p1(new int(1));//直接初始化
    shared_ptr<int> p2 = new int(1);//错误,int是内置类型

接受指针参数的智能指针构造函数是explicit的,必须使用直接初始化,不能做隐式类型转换

shared_ptr<int> FUn(int p) 
{
    return new int(p); //error
    return shared_ptr<int>(new int(p)); //right
}

定义自己的释放操作

shared_ptr<T> p(q) //p管理内置指针q,q必须指向new分配的内存,且能够转换为T*类型

shared_ptr<T> p(u) //p从unique_ptr u那里接管了对象的所有权,并将u置空

shared_ptr<T> p(q, d) //p接管了内置指针q所指向的对象的所有权,q必须能够转换为T*类型。p将使用可调用对象d来代替delete

shared_ptr<T> p(p2, d) //p是shared_ptr p2的拷贝,调用d来代替delete

//删除器必须接受所指定类型的参数,如,上述中就要接受一个类型为T*的参数

p.reset() 
p.reset(q)
p.reset(q, d)
//若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,则会调用d而不是delete来释放q

//例如:
void del(int* p)
{...}
shared_ptr<int> p(new int(1), del);
shared_ptr<int> p(new int, del);

智能指针的陷阱和缺陷

  • 不要混合使用智能指针和普通指针 
void process(shared_ptr<int> ptr) 
{
    ...
}
//ptr离开作用域,被销毁
int* x(new int(1024));
process(x); //error
process(shared_ptr<int>(x)); //合法,但内存会被释放
int j = *x; //未定义,x是一个空悬指针

//使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时被销毁
  • 永远不要使用get初始化另一个智能指针或为智能指针赋值
    智能指针定义的get函数返回值是一个内置指针,指向智能指针管理的对象,所以结合我们上文所述,这样有可能导致产生空悬指针,从而发生未定义的行为。

此函数是为了:需要向不能使用智能指针的代码传递一个内置指针。

void f(int* q) 
{
    shared_ptr<int> tmp(q);
}//释放了q指向的内容
int main()
{
    shared_ptr<int> p(new int(1));
    int* q = p.get();
    f(q);
    int qq = *q; //未定义的行为
}
//对上述代码编译器并不会给出错误信息
get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get

unique_ptr

unique_ptr的直观认知应该就是“独占”、“拥有”了吧。它的意思就是某个时刻只能有一个unique_ptr指向一个给定的对象,即不能拷贝和赋值

    int* p (new int(3));
    shared_ptr<int> p1(p);
    auto p2 = p1; //ok
    unique_ptr<int> p3(p);
    unique_ptr<int> p4(p); //ok,智能指针毕竟只是一个帮助你管理的一个工具,它并不能知道有多少初始化的动作
    unique_ptr<int> p5 = p3; //error
  • 初始化 
    unique_ptr没有类似make_shared的操作,只能直接初始化
    unique_ptr<int> p(new int(1024)); //ok
    unique_ptr<int> p1 = new int; //error
    unique_ptr<int> p2(p); //error
  • unique_ptr基操

    unique_pre<T> p1; //空unique_ptr,可以指向类型为T的对象,p1会使用delete来释放它的指针,p2会使用一个类型为D的可调用对象来释放它的指针  

    unique_ptr<T, D> p2; 
    unique_ptr<T, D> p(d); //空unique_ptr,指向类型为T的对象,用类型为D的对象d来代替delete

    p = nullptr; //释放p指向的对象,将p置为空
    p.release(); //p放弃对指针的控制权,返回指针,并将p置空
    p.reset(); //释放p所指向的对象
    p.reset(q); //如果提供了内置指针q,令p指向这个对象;
    p.reset(nullptr); 
  • 虽然我们不能拷贝和赋值,但我们可以调用上述所说的reset和release将指针的所有权从一个(非const)unqiue_ptr转移给另一个unique_ptr
//将所有权从p1转移给p2
    (1)unique_ptr<string> p2(p1.release());
    (2)p2.reset(p1.release());
  • 单纯调用release是错误的
p.release(); //错误,release放弃了控制权不会释放内存,丢失了指针
auto q = p.release(); //正确,记得delete掉q
  • 特殊的版本
    unique_ptr针对new出来的数组特殊化,是一个特殊化的版本
unique_ptr<int []> q(new int[10]);
q.release();
//自动用delete[]销毁其指针释放内存
  • unique_ptr作为参数传递和返回值
    unique_ptr的不能拷贝有一个例外:
unique_ptr<int> Fun(int p) 
{
    return unique_ptr<int>(new int(p));
}
//返回一个局部对象的拷贝
unique_ptr<int> Fun(int p) 
{
    unique_ptr<int> ret(new int(p));
    //...
    return ret;
}

编译器知道要返回的对象将要被销毁,执行了一种特殊而“拷贝”(移动操作)
 

  • 23
    点赞
  • 108
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
shared_ptr是C++中的智能指针,它采用引用计数的方法来实现释放指针所指向的资源。当使用shared_ptr时,它会记录有多少个shared_ptr指向同一个对象,只有当最后一个shared_ptr被销毁时,该对象的内存才会被释放。因此,shared_ptr可以自动管理内存,不需要手动释放。 在代码中,使用shared_ptr可以像普通指针一样操作对象。当需要创建一个shared_ptr对象时,可以使用std::make_shared函数来构造,如下所示: ``` std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(10); ``` 可以通过将shared_ptr赋值给其他shared_ptr来共享资源,如下所示: ``` std::shared_ptr<int> sharedPtr2 = sharedPtr1; ``` 当所有的shared_ptr都被销毁时,内存会自动释放。可以使用use_count()函数获取shared_ptr的引用计数,如下所示: ``` std::cout << "sharedPtr2 use count: " << sharedPtr2.use_count() << std::endl; ``` 当引用计数为0时,内存会被自动释放。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++智能指针shared_ptr分析](https://download.csdn.net/download/weixin_38705004/13788082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C++11中的智能指针unique_ptrshared_ptr和weak_ptr详解](https://blog.csdn.net/chenlycly/article/details/130918547)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值