美文网首页
C++智能指针详解

C++智能指针详解

作者: Bfmall | 来源:发表于2023-09-26 13:27 被阅读0次

    引入

    指针是C++最繁琐的地方,搞不好就存在内存泄漏,在析构函数中还要仔细考虑如何delete掉你用的指针;
    (new出来的都是要还的)

    在一个大型程序中,指向动态分配内存的指针可能会在程序的各个部分使用。在这种情况下,确定哪些内存不再需要,或者程序的哪个部分应该负责删除指针就变得比较困难。

    程序可能会因此出现悬挂指针,也就是说,指针已经被删除了,但其内存仍然在使用中;还可能出现内存泄漏,也就是说,即使已经不再需要内存了,但指针仍然未被删除。另外还有双重删除的问题,当程序的某 部分要删除一个已经被删除的指针时,即可出现这种情况。如果被删除的内存已经进行了重新分配,则双重删除会对程序造成破坏。

    而C++11它来了,引入智能指针的概念来解决指针的问题

    智能指针类型

    C++11 提供了 3 种智能指针类型,它们分别由 u n i q u e _ p t r unique_ptrunique_ptr 类(独占指针)、s h a r e d _ p t r shared_ptrshared_ptr 类(共享指针)和 w e a k _ p t r weak_ptrweak_ptr 类(弱指针)定义。

    智能指针背后的核心概念是动态分配内存的所有权。智能指针被称为可以拥有或管理它所指向的对象。当需要让单个指针拥有动态分配的对象时,可以使用独占指针。对象的所有权可以从一个独占指针转移到另一个指针,其转移方式为:对象始终只能有一个指针作为其所有者。当独占指针离开其作用域或将要拥有不同的对象时,它会自动释放自己所管理的对象。

    共享指针将记录有多少个指针共同享有某个对象的所有权。当有更多指针被设置为指向该对象时,引用计数随之增加;当指针和对象分离时,则引用计数也相应减少。当引用计数降低至0时,该对象被删除。

    unique_ ptr、shared_ ptr 和 weak_ ptr 类是在 memory 头文件中定义的,所以需要在使用它们的程序中包含以下语句:#include <memory>

    智能指针的使用

    智能指针实际上是一个对象,在对象的外面包围了一个拥有该对象的普通指针。这个包围的常规指针称为裸指针。

    为了避免内存泄漏,通过智能指针管理的对象应该没有其他的引用指向它们。换句话说,指向动态分配存储的指针应该立即传递给智能指针构造函数,而不能先将它赋值给指针变量。

    智能指针不支持指针的算术运算,所以下面的语句将导致编译器错误

    uptr1 ++;
    uptr1 = uptr1 + 2;
    

    智能指针通过运算符重载支持常用指针运算符 * 和 ->

    unique_ptr类使用注意事项
    不能使用其他 unique_ptr 对象的值来初始化一个 unique_ptr。同样,也不能将一个 unique_ptr 对象赋值给另外一个。这是因为,这样的操作将导致两个独占指针共享相同对象的所有权,所以,以下语句都将出现编译时错误:

    unique_ptr<int> uptr1(new int);
    unique_ptr<int> uptr2 = uptr1; // 非法初始化
    unique_ptr<int> uptr3; // 正确
    uptr3 = uptr1; // 非法赋值
    

    要想转移所有权可以使用unique_ptr提供的move()方法, 用于将对象的所有权从一个独占指针转移到另外一个独占指针。

    永远不要试图去动态分配一个智能指针,相反,应该像声明函数的局部变量那样去声明智能指针。当 unique_ptr 将要离开作用域时,它管理的对象也将被删除。如果要删除智能指针管理的对象,但同时又保留智能指针在作用域中,则可以将其值设置为 nullptr,或者调用其 reset() 成员函数

    uptr = nullptr;
    //或者使用reset()方法
    uptr.reset();
    

    shared_ptr的应用场景
    智能指针shared_ptr能够帮助我们对程序中的内存资源进行很好的管理,避免内存泄漏或者内存访问错误的发生,但是也不能乱用,它增加了额外的引用计数而牺牲了一定的性能。如果在不需要shared_ptr的场景下过度滥用shared_ptr,不仅无助于内存资源的管理,反而可能会影响程序的性能,最终得不偿失。

    使用场景:

    有多个使用者共同使用同一个对象,而这个对象没有一个明确的拥有者;
    这个在多线程操作中使用shared_ptr很方便,可以很自然的用这个指针而不能担心内存泄漏的问题。

    某一个对象的复制操作很费时;
    如果一个对象的复制操作很费时,同时我们又需要在函数间传递这个对象,我们往往会选择传递指向这个对象的指针来代替传递对象本身,以此来避免对象的复制操作。既然选择使用指针,那么使用shared_ptr是一个更好的选择,即起到了向函数传递对象的作用,又不用为释放对象操心。

    要把指针存入标准库容器时;
    不管容器中保存的是普通指针还是智能指针,在使用上,两者并无太大区别,使用智能指针的优越性主要体现在容器使用完毕后清空容器的操作上。如果容器中保存的是普通指针,当我们在清空某个容器时,先要释放容器中指针所指向的资源,然后才能清空这些指针本身。

    使用裸指针在析构的时候就先要释放容器中普通指针所指向的资源,然后才能释放指针清空容器:

    class SalarySys
    {
    // …
    public:
    ~SalarySys()
      {
     // …
      // 首先,释放容器中普通指针所指向的资源
     for(Employee* p : m_vecEmp)
      {
      delete p; // 释放指针所指向的对象
      }
      // 然后,用clear()函数清空容器
     m_vecEmp.clear();
      }
    // …
    private:
     vector<Employee*> vecEmp; // 保存普通指针的容器
    }
    

    而我们使用强引用指针shared_ptr存放的话这个过程就很简单了:

    class SalarySys
    {
    // …
    public:
    ~SalarySys()
      { 
      // …
    // 用clear()函数释放容器中的shared_ptr
    // shared_ptr在释放的时候也会连带地释放它所管理的Employee对象
     m_vecEmp.clear();
      }
    // …
    private:
     vector<shared_ptr<Employee>> vecEmp; // 保存shared_ptr的容器
    }
    

    当需要特殊清除方式的资源时,可以通过定制shared_ptr的删除器来实现
    weak_ptr使用场景
    这个指针一般不单独使用,是和shared_ptr搭配使用的,甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。

    当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。

    这个解决了一个很大的问题:强引用指针的相互引用问题


    image.png

    当类A中有一个指向类B的shared_ptr强类型智能指针,类B中有一个指向类A的shared_ptr强类型智能指针

    此时,有两个强智能指针指向了对象A,对象A的引用计数为2,

    也有两个强智能指针指向了对象B,对象B的引用计数为2;

    这就会有相互引用无法释放的问题;

    解决方案:将类A和类B中的shared_ptr强智能指针都换成weak_ptr弱智能指针。
    (创建对象的时候持有它的强智能指针,当其他地方想使用这个对象的时候,应该持有该对象的弱智能指针)

    class B;
    class A
    {
    public:
        A(){cout<<"A()"<<endl;}
        ~A(){cout<<"~A()"<<endl;}
        weak_ptr<B> m_ptrb; //其他地方持有对象的弱智能指针
    };
    
    class B
    {
    public:
        B(){cout<<"B()"<<endl;}
        ~B(){cout<<"~B()"<<endl;}
        weak_ptr<A> m_ptra; //其他地方持有对象的弱智能指针
    };
    
    int main(int argc, char* argv[])
    {
        shared_ptr<A> ptra(new A()); //创建对象时持有强智能指针
        shared_ptr<B> ptrb(new B()); //创建对象时持有强智能指针
    
        ptra->m_ptrb = ptrb;
        ptrb->m_ptra = ptra;
    
        return 0;
    }
    
    成员方法 功能
    operator=() 重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
    swap(x) 其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
    reset() 将当前 weak_ptr 指针置为空指针。
    use_count() 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
    expired() 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
    lock() 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

    weak_ptr使用总结
    创建对象的时候用shared_ptr强智能指针,别的地方一律持有weak_ptr弱智能指针,否则析构顺序有可能出现错误。
    当通过弱智能指针访问对象时,需要先进行lock提升操作,提升成功,证明对象还存在,再通过强智能指针访问对象

    ————————————————
    版权声明:本文为CSDN博主「AlbertOS」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Albert_weiku/article/details/125715913

    相关文章

      网友评论

          本文标题:C++智能指针详解

          本文链接:https://www.haomeiwen.com/subject/wwkhbdtx.html