C++之“智能指针”

一、共享资源的智能指针 【shared_ptr】


       1. 引入
                要确保用 new 动态分配的内存空间在程序的各条执行路径都能被释放是一件麻烦的事情。
                C++11 模板库的 <memory> 头文件中定义的智能指针,即 shared_ptr 模板,就是用来部分解决这个问题的。
                
                只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,就不需要再单独编写delete p语句,
                托管 p 的 shared_ptr 对象在消亡时会自动执行delete p。而且,该 shared_ptr 对象能像指针 p 一样使用,
                即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。
            
       2. 使用说明
                通过 shared_ptr 的构造函数,可以让 shared_ptr 对象托管一个 new 运算符返回的指针,写法如下: 
                    shared_ptr<T> ptr(new T);  // T 可以是 int、char、类等各种类型
                //此后,ptr 就可以像 T* 类型的指针一样使用,即 *ptr 就是用 new 动态分配的那个对象。
                //shared_ptr是一种计数指针,当shared_ptr所指对象的引用计数变为0时,该对象就会被自动删除。
            
       3. 注意
                a. 多个 shared_ptr 对象可以共同托管一个指针 p。
                    ★★★当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行delete p。
                
                b. 只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。
                    若将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错。
                        因为不能析构一个并没有指向动态分配的内存空间的指针。
                        
                c. 因为智能指针是模拟指针的模板类,目的是自动回收堆,故必须给智能指针传递“堆”,在给shared_ptr分配内存时使用make_shared函数更安全。
            
       4. 实例
                

#include <iostream>
#include <memory>
using namespace std;
                
class A{
    public:
        int i;
        A(int n):i(n) { }
        ~A() { cout << i << " " << "destructed" << endl; }
};

int main()
{
    shared_ptr<A> sp1 = make_shared<A>(A(2));    //较下面的更安全
    //shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
    shared_ptr<A> sp2(sp1);  //A(2)同时交由sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同时交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get返回托管的指针,p 指向 A(2)
    cout << p->i << endl;   //输出 2
    sp1.reset(new A(3));    // reset导致托管新的指针, 此时sp1托管A(3)
    sp2.reset(new A(4));    // sp2托管A(4)
    cout << sp1->i << endl; //输出 3
    sp3.reset(new A(5));    // sp3托管A(5),A(2)无人托管,被delete
    cout << "end" << endl;
    return 0;
}

          程序的输出结果如下:
                    2,2,2
                    2
                    3
                    2 destructed
                    end
                    5 destructed
                    4 destructed
                    3 destructed
             
          /**解析**/
            ⑴ 让多个 sharecLptr 对象托管同一个指针时,这多个 shared_ptr 对象会共享一个对共同托管的指针的“托管计数”。  【即当有 n 个 shared_ptr 对象托管同一个指针 p时,则 p 的托管计数就是 n。】
            ⑵ 当一个指针的托管计数减为 0 时,该指针会被释放。【shared_ptr 对象消亡或托管了新的指针,都会导致其原托管指针的托管计数减 1】
            ⑶ shared_ptr 的 reset 成员函数可以使得对象解除对原托管指针的托管(如果有的话),并托管新的指针,原指针的托管计数会减 1。
            ⑷ 输出的第4行说明:用 new 创建的动态对象 A(2) 被释放了。(程序中没有写 delete 语句,而 A(2) 被释放)
            是因为程序的 sp3.reset(new A(5)); 执行后,已经没有 shared_ptr 对象去托管 A(2),于是 A(2) 的托管计数变为 0。
                 【最后一个解除对 A(2) 托管的 shared_ptr 对象会去释放 A(2)】     
            ⑸ main函数结束时,sp1、sp2、sp3 对象消亡,各自将其托管的指针的托管计数减为 0,并且释放其托管的指针,于是会有以下输出:
                        5 destructed
                        4 destructed
                        3 destructed
            
       5. 拓展
                注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针。如: 
                    A* p = new A(10);
                    shared_ptr <A> sp1(p), sp2(p);
                    
                sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1 (sp2 无法知道 p 已经被 sp1 托管过)。
                这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
           


二、 【unique_ptr】


            1. 说明
                unique_ptr 独占所指向的对象, 同一时刻只能有一个 unique_ptr 指向给定对象(通过禁止拷贝语义, 只有移动语义来实现)
                
            2. 实例
                std::unique_ptr<int> p1(new int(5));
                std::unique_ptr<int> p2=p1;  // 编译会出错, unique_ptr 不支持赋值
                std::unique_ptr<int> p2(p1);  // 错误, unique_ptr 不支持拷贝
                std::unique_ptr<int> p3=std::move(p1);  // 转移所有权,那块内存归p3所有, p1成为无效的针.
                p3.reset();  //释放内存.
                p1.reset();  //无效
                
            3. 成员函数
                (1) get 获得内部对象的指针, 由于已经重载了()方法, 因此和直接使用对象是一样的.如 unique_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的
                (2) release 放弃内部对象的所有权,将内部指针置为空, 返回所内部对象的指针, 此指针需要手动释放
                (3) reset 销毁内部对象并接受新的对象的所有权(如果使用缺省参数的话,也就是没有任何对象的所有权, 此时仅将内部对象释放, 并置为空)
                (4) swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
                (5) move 所有权转移(通过移动语义)。[std::move(up); up所有权转移后,变成“空指针” (up 的定义为 std::unique_ptr<Ty> up)]

 
三、弱指针 【weak_ptr】


            1. 说明
                weak_ptr指向一个已经用shared_ptr进行管理的对象。
                为了访问这个对象,一个 weak_ptr 可以通过 shared_ptr 的构造函数或是 weak_ptr 的成员函数 lock() 转化为一个shared_ptr。
                
                weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 
                它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
                
                weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。 注意, weak_ptr 在使用前需要检查合法性。
                
                weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数。
                
            2. 成员函数
                (1) expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false。
                (2) lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同。
                (3) use_count 返回与 shared_ptr 共享的对象的引用计数。
                (4) reset 将 weak_ptr 置空。
                
            3. 使用"weak_ptr"解决"shared_ptr"因循环引用而不能释放资源的问题
                使用 shared_ptr 时, shared_ptr 为强引用, 如果存在循环引用, 将导致内存泄露。而 weak_ptr 为弱引用, 可以避免此问题。 其原理:
                  对于弱引用来说, 当引用的对象活着的时候弱引用不一定存在. 仅仅是当它存在的时候的一个引用, 弱引用并不修改该对象的引用计数, 这意味这弱引用它并不对对象的内存进行管理。
                  weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。
                
                注意:虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出现了循环引用, 还是会造成内存泄漏。
       

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值