• 智能指针剖析


    1. auto_ptr
    已经废弃。原因是它行为上是"排它性"指针,但又允许编译器实现拷贝操作,拷贝后的右值会被赋空。即将“传递”语义掩盖在“拷贝”动作之下。
    即a=b时,作为右值的b的物理指针会是NULL。
    会造成使用它的容器混乱。
    这是典型的设计缺陷。既然是“传递”语义,就不应以“拷贝"形式出现。
    另一方面,它对于数组的指针也支持不好,无法完成new []和delete []的配对。
     
    1. unique_ptr
    这是对auto_ptr的“传递”语义的正确实现。从语言层面来保证不出现“拷贝”动作。
     
     1 typedef std::unique_ptr<int> unique_t;
     2 typedef std::vector< unique_t > vector_t;
     3 
     4 vector_t vec1;                           // fine
     5 vector_t vec2(5, unique_t(new Foo));     // Error (Copy)
     6 vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
     7 vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
     8     // Courtesy of sehe
     9 
    10 std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator
    11 
    12 std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)
    1. shared_ptr
    不像之前的unique_ptr,shared_ptr重点在于shared,即为了多个指针指向同一对象而生。同时,又在原生指针基础之上,加了引用计数。在如a=b的场景下,会自动增减引用的计数。如a如果原来指向一个对象,则由于此赋值动作,原先被指向的对象计数要减1,同时,b指向的对象计数要加1。
     
    引用计数通过如下图的control block表示,总的的内存分配:
     
    可见:
    - shared_ptr占用空间是正常指针的两倍;
    - control block记录了引用计数,同时还有其他的一些自定义的deleter等。
     
    control block本身也是动态分配的,它在如make_shared,或由unique_ptr、raw ptr初始化时创建。
    为此,要避免对于raw指针,多次重复创建shared_ptr,导致同一个object多个control block的情况。这会引起重复析构!
    分以下情况避免:
    如果由于需要定制化的deleter而不得不使用new时,要将其写在一行语句中。
    1 std::shared_ptr<Widget> spw1(new Widget, xxxDel);

    这样。

     
    对于有的需要this指针的场合,有专门的语言层面解决方法:enable_shared_from_this,但需要调用前本对象已经有control block,即已经有shared_ptr指向本身。所以一般用在工厂模式中。
     
     
    衡量下control block的具体开销:
    1. 大小,如上所述指针的大小外,本身的大小不会太大,一般也就3个字长;
    2. 解引用开销:可见,解引用是无额外开销的;
    3. 引用计数:原子操作的开销,与具体机器有关,但往往也是一条指令完成;
    4. 它内部还存在虚函数的调用。
     
    如果对开销敏感,同时又需要“execlusive"语义,unique_ptr才是选择。unique还支持对[]T的指针,虽然没啥用。unique_ptr它可以转成shared_ptr,但反过来不行。
     
    1.   weak_ptr
    weak_ptr纯粹是作为shared_ptr的补充而存在。它不能解引用,不能判空,生命周期开始之初就需要与shared_ptr共存(只有通过shared_ptr才能初始化)。
    它的作用是,在不占用shared_ptr引用计数的同时,判断出一个对象是否销毁。即判断shared_ptr中的引用计数是否为0了。(dangle指针)。这种状态对于weak_ptr来说有个专有名词,叫做expired。
     
    这种检查有两种形式:
    a. 通过weak_ptr的lock(),“原子性”的判断是否expired,如果是,返回shared_ptr,否则返回空;
    b. 直接用来初始化一个shared_ptr,如果expired,则异常。
     
    应用场景:
    1. 是可用于像观察者模式这种,需要持有某对象的指针,但这个“持有”动作并不应影响对象的生命周期。(观察者是否销毁不应由持有指针的subject影响);另一方面,subject又需要知道observer是否存活,这种情况,需要shared_ptr加weak_ptr的配合;
    2. 有一种场景,A与B需要互相持有对方指针,如果是shared_ptr,则会出现java式“内存泄漏”。需要用weak_ptr破除这种环形引用。
     
     
    1. 初始化与control block
    使用new与使用make_shared初始化的区别:
    - 需要明确的是,control block也是需要动态申请的内存。 make_shared时,可能会对shared_ptr的对象的内存与control block的内存一起申请。会在内存对齐,申请速度上有优势;
    - 另一方面,new与shared_ptr两者分开进行时,如果编译器在new与shared_ptr初始化过程中,优化插入了一些可能抛出异常的代码,则会有内存泄漏。
     
    - 但,在需要定制化deleter时,也需要new,这种代码要小心,写成异常安全的;
    - 在构造函数初始化时,有传入{}和()的区别,特别对于如vector这种object,如果使用make_shared,要注意默认传的是()。即:
         auto p = std::make_shared<std::vector<int> >(10, 20);
      这种代码,到底是初始化了一个vecotr包含10, 20两个数字,还是10个元素的vector每一个都是20?
    - 最需要理解的一点:如果是make_shared出来的内存,由于control block与object内存是绑定一起申请的,那么也要一起释放。而control block中,引用计数的值不仅shared_ptr会看,weak_ptr也会看。这样即使对象没有引用,引用计数为0了,但如果有weak_ptr存在,这control block就不能释放(要给weak_ptr判断用)。control block中的weak_counter就是记录weak_ptr的引用计数的。这在极端情况下,可能会造成一些影响。
     
  • 相关阅读:
    Macos中Office的选择
    基于上一篇的滑动列表,动态添加/删除功能
    GitLab 服务搭建及常用命令
    java多线程中的异常处理
    java实现xml互转json
    nginx集群搭建方案(教你如何搭建nginx集群)
    cmd窗口改字符集编码
    ThreadLocal详解
    查看Linux系统版本信息的几种方法
    Windows命令之tasklist命令
  • 原文地址:https://www.cnblogs.com/qqmomery/p/6224275.html
Copyright © 2020-2023  润新知