C++智能指针之shared_ptr、unique_ptr、weak_ptr

#include <iostream>
#include <vector>
#include <memory>
using namespace std;


/*
 shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。
 标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,
 这三种智能指针都定义在memory头文件中。
 */

#pragma mark shared_ptr类
//MARK:shared_ptr类
//TODO:shared_ptr类
//FIXME:shared_ptr类
/*
 创建智能指针时必须在尖括号中提供额外的信息,即:智能指针所封装的指针,是什么类型的指针。
 shared_ptr<string> p1;
 shared_ptr<list<int>> p2;
 默认初始化的智能指针中保存着一个空指针。
 
 智能指针的使用方式和普通指针类似(因为它重载了->操作符),解引用一个智能指针返回它指向的对象
 shared_ptr<string> p2(new string("123"));
 if (p2 == nullptr) {
     cout << "封装了一个空指针" << endl;
 }else{
     auto temp = *p2;//temp就是string类型
     cout << temp << endl;
 }
 
 在一个条件判断中使用智能指针就是检测它是不是封装了一个空指针。
 shared_ptr<string> p1;
 if (!p1) {
     cout << "封装了一个空指针" << endl;
 }
 
 if (p1 == nullptr) {
     cout << "封装了一个空指针" << endl;
 }
 
 shared_ptr和unique_ptr都支持的操作
 shared_ptr<T> sp       空智能指针,可以指向类型T的对象
 unique_ptr<T> up       空智能指针,可以指向类型T的对象
 p                      将p用作一个条件判断,若指向一个对象,则为true
 *p                     解引用p,获得指向的对象
 p->mem                 等价于(*p).mem
 p.get()                返回p中封装的指针,要小心使用。若智能指针释放了其封装的对象
                        即:p.reset(),返回的为空指针。
 std::swap(p,q)         交换p和q中的指针
 p.swap(q)              交换p和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.user_count()为1,返回ture;否则返回false
 p.use_count()          返回与p共享对象的智能指针数量。
 
 
 make_shared函数(std::make_shared是C++11的一部分,但是std::make_unique很可惜不是。它是在C++14里加入标准库的):
 最安全的分配和使用动态内存的方法就是调用一个名为make_shared的标准库函数,
 此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。头文件和share_ptr相同,在尖括号中
 必须指定想要创建对象的类型,定义格式见下面例子:
 shared_ptr<int> p3 = make_shared<int>(42);
 shared_ptr<string> p4 = make_shared<string>(10,'9');
 shared_ptr<int> p5 = make_shared<int>();
 
 make_shared用其参数来构造给定类型的对象,如果我们不传递任何参数,对象就会进行默认值初始化。
 
 shared_ptr的拷贝和赋值
 当进行拷贝和赋值时,shared_ptr都会记录总共有多少个shared_ptr指向相同的对象,使用use_count()获取个数。
 个数为0时,智能指针会对其封装的对象指向做delete操作,这样你就省心了。
 shared_ptr<int> p1 = make_shared<int>();
 cout << p1.use_count() << endl;
 auto q(p1);
 cout << p1.use_count() << endl;
 auto t = q;
 cout << p1.use_count() << endl;
 
 
 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论何时我们拷贝一个shared_ptr,计数器都会递增。
 当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减,
 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
 auto r = make_shared<int>(42);//r指向的int只有一个引用者
 r=q;//给r赋值,令它指向另一个地址
     //递增q指向的对象的引用计数
     //递减r原来指向的对象的引用计数
     //r原来指向的对象已没有引用者,会自动释放
 
 shared_ptr自动销毁所管理的对象
 当指向一个对象的最后一个shared_ptr被销毁时(注意不同于:被赋予了新增,哪怕reset()空值),shared_ptr类会自动销毁此对象,它是通过另一个特殊的成员函数-析构函数完成销毁工作的,
 类似于构造函数,每个类都有一个析构函数。析构函数控制对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。

 shared_ptr还会自动释放相关联的内存
 当动态对象不再被使用时,shared_ptr类还会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

 
 shared_ptr和new结合使用
 如果我们不初始化一个智能指针,它就会被初始化成一个空指针,接受指针参数的智能指针是explicit的,
 因此我们不能将一个内置指针隐式转换为一个智能指针,必须直接初始化形式来初始化一个智能指针
 shared_ptr<int> p1 = new int(1024);//错误:必须使用直接初始化形式
 shared_ptr<int> p2(new int(1024));//正确:使用了直接初始化形式
 
 定义、改变shared_ptr的其他方法:
 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必须指向new分配的内存,且能够转换为T*类型;p将
                            使用可调用对象d代替delete
 class Person {
 public:
     Person(){cout << "Person构造" << endl;}
     ~Person(){cout << "Person析构" << endl;}
 };

 void del(Person *p){
     delete p;
 }
 
 void test_shared_ptr(){
     shared_ptr<Person> p1(new Person(), del);
 }
 
 p.reset()          若p是唯一指向其对象的shared_ptr,reset会释放此对象。
 p.reset(q)         若传递了可选的参数内置指针q,会令p指向q,当然原来所指向的对象引用计数减一
 p.reset(q, d)      若还传递了参数d,将会调用d而不会是delete来释放q
 
 
 其他shared_ptr操作
 可以使用reset来将一个新的指针赋予一个shared_ptr:
 p = new int(1024);//错误:不能将一个指针赋予shared_ptr
 p.reset(new int(1024));//正确。p指向一个新对象
 
 与赋值类似,reset会更新引用计数,如果需要的话,会释放p的对象。
 reset方法经常和unique方法一起使用,来控制多个shared_ptr共享的对象。
 在改变底层对象之前,我们检查自己是否是当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝:
 shared_ptr<Person> p1(new Person());
 auto p2(p1);
 
 if (p2.unique() == false) {
     p2.reset(new Person());
 }
 
 智能指针和异常
 使用智能指针,即使程序块过早结束,智能指针也能确保在内存不再需要时将其释放:
 sp是一个shared_ptr,因此sp销毁时会检测引用计数,当发生异常时,我们直接管理的内存是不会自动释放的。
 如果使用内置指针管理内存,且在new之后在对应的delete之前发生了异常,则内存不会被释放。

 使用我们自己的释放操作
 默认情况下,shared_ptr假定他们指向的是动态内存,因此当一个shared_ptr被销毁时,会自动执行delete操作,为了用shared_ptr来管理一个connection,我们必须首先必须定义一个函数来代替delete。这个删除器函数必须能够完成对shared_ptr中保存的指针进行释放的操作。

 智能指针陷阱:
 (1)不要使用相同的“内置指针”值初始化(或reset)多个智能指针。
 (2)不要delete get()返回的指针
 (3)不要使用get()初始化或reset另一个智能指针
 (4)如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
 (5)如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
 */

//MARK:unique_ptr
/*
 某个时刻只能有一个unique_ptr指向一个给定对象,由于一个unique_ptr拥有它指向的对象,
 因此unique_ptr不支持普通的拷贝或赋值操作。
 
 unique操作:
 unique_ptr<T> u1           空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针
 unique_ptr<Person> up;
 if (!up) {
     cout << "封装了一个空指针\n";
 }
 
 if (up == nullptr) {
     cout << "封装了一个空指针\n";
 }
 
 uniuqe_ptr<T, D> u2           u2会使用一个类型为D(struct class均可)的可调用对象来释放它的指针
 struct D {
     void operator()(Person* p){
         delete p;
     }
 };
 void test_ptr(){
     unique_ptr<Person, D> up(new Person());
 }

uniuqe_ptr<T, D> u(d)     空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr               释放u指向的对象,将u置为空
u.release()               u放弃对指针的控制权,返回指针,并将u置为空
u.reset()                 释放u指向的对象
u.reset(q)                如果提供了内置指针q,令u指向这个对象,
u.reset(nullptr)          释放u指向的对象,将u置为空
 
虽然我们不能拷贝或者赋值unique_ptr,
但是可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique
 //将所有权从p1(指向string Stegosaurus)转移给p2
 unique_ptr<string> p2(p1.release());//release将p1置为空
 unique_ptr<string> p3(new string("Trex"));
 //将所有权从p3转移到p2
 p2.reset(p3.release());//reset释放了p2原来指向的内存
 
 release方法返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为p1原来保存的指针,而p1被置为空。
 reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。
 调用release会切断unique_ptr和它原来管理的的对象间的联系。
 release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。

 不能拷贝unique_ptr有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr;
 最常见的例子是从函数返回一个unique_ptr.
 unique_ptr<Person> clone0(Person *p)
 {
     //正确:从Person*创建一个unique_ptr<Person>
     return unique_ptr<Person>(p);
 }

 unique_ptr<Person> clone1(Person *p)
 {
     unique_ptr<Person> ret(p);
     return ret;
 }

 void test_ptr(){
     unique_ptr<Person> p0;
     p0 = clone0(new Person());
     unique_ptr<Person> p1;
     p1 = clone1(new Person());
 }
 */

#pragma mark weak_ptr
/*
 weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象;
 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
 
 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock,
 此函数检查weak_ptr指向的对象是否存在。如果存在,
 lock返回一个指向共享对象的shared_ptr,如果不存在,lock将返回一个空指针

 shared_ptr<Person> p0(new Person());
 cout << p0.use_count() << endl;
 
 weak_ptr<Person> wp = p0;
 cout << p0.use_count() << endl;
 
 //如何使用weak_ptr
 shared_ptr<Person> p = wp.lock();
 if (p) {
     cout << "使用" << endl;
 }else{
    cout << "weak_ptr指向的对象是已不存在" << endl;
 }
 
 */

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值