写点什么

c++11&14- 智能指针

用户头像
赖猫
关注
发布于: 2021 年 03 月 24 日

学 c++的人都知道,在 c++里面有一个痛点,就是动态内存的管理,就我所经历的一些问题来看,很多莫名其妙的问题,最后都发现是内存管理不当引起的。


但像 java 等其他一些语言则不会有这样的问题,为什么呢,因为它们有很好的处理内存的方法,比如 java 的垃圾回收机制,现在,我们 c++终于也有了智能指针。


1. 什么是智能指针


简单地说,智能指针是用对象去管理一个资源指针,同时用一个计数器计算引用当前指针对象的个数,当管理指针的对象增加或减少时,计数器也相应加 1 或减 1,当最后一个指针管理对象销毁时,计数器为 1,此时在销毁指针管理对象的同时,也对指针管理对象所管理的指针进行 delete 操作。


下面我们介绍两个常用的智能指针 std::shared_ptr 和 std::weak_ptr。


1.1 std::shared_ptr


std::shared_ptr 包装了 new 操作符动态分配的内存,可以自由拷贝复制,基本上是使用最多的一个智能指针类型。


下面是一个代码例子:


#include <memory>#include <iostream>class Test{public:    Test()    {        std::cout << "Test()" << std::endl;    }    ~Test()    {        std::cout << "~Test()" << std::endl;    }};int main(){    std::shared_ptr<Test> p1 = std::make_shared<Test>();    std::cout << "1 ref:" << p1.use_count() << std::endl;    {        std::shared_ptr<Test> p2 = p1;        std::cout << "2 ref:" << p1.use_count() << std::endl;    }    std::cout << "3 ref:" << p1.use_count() << std::endl;    return 0;}
复制代码


结果如下:


Test()1 ref:12 ref:23 ref:1~Test()
复制代码


针对代码解读如下:


  • std::make_shared 里面调用了 new 操作符分配内存;

  • 第二个 p1.use_count()之所以显示为 2,是因为增加了引用对象 p2,而随着大括号的结束,p2 的作用域结束,所以 p1 的引用计数变回 1,而随着 main 函数的结束,p1 的作用域结束,此时检测到计数为 1,那就会在销毁 p1 的同时,调用 p1 的析构函数 delete 掉之前分配的内存空间;


1.2 std::weak_ptr


std::weak_ptr 有什么特点呢?与 std::shared_ptr 最大的差别是在赋值的时候,不会引起智能指针计数增加。


weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造,获得资源的观测权。但 weak_ptr 没有共享资源,它的构造不会引起指针引用计数的增加。同样,在 weak_ptr 析构时也不会导致引用计数的减少,它只是一个静静地观察者。weak_ptr 没有重载 operator*和->,这是特意的,因为它不共享指针,不能操作资源,这是它弱的原因。如要操作资源,则必须使用一个非常重要的成员函数 lock()从被观测的 shared_ptr 获得一个可用的 shared_ptr 对象,从而操作资源。


1.2.1 std::shared_ptr 相互引用会有什么后果


代码如下:


#include <memory>#include <iostream>class TestB;class TestA{public:    TestA()    {        std::cout << "TestA()" << std::endl;    }    void ReferTestB(std::shared_ptr<TestB> test_ptr)    {        m_TestB_Ptr = test_ptr;    }    ~TestA()    {        std::cout << "~TestA()" << std::endl;    }private:    std::shared_ptr<TestB> m_TestB_Ptr; //TestB的智能指针}; class TestB{public:    TestB()    {        std::cout << "TestB()" << std::endl;    }    void ReferTestB(std::shared_ptr<TestA> test_ptr)    {        m_TestA_Ptr = test_ptr;    }    ~TestB()    {        std::cout << "~TestB()" << std::endl;    }    std::shared_ptr<TestA> m_TestA_Ptr; //TestA的智能指针};int main(){    std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();    std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();    ptr_a->ReferTestB(ptr_b);    ptr_b->ReferTestB(ptr_a);    return 0;}
复制代码


运行结果:


TestA()TestB()
复制代码


可以看到,上面代码中,我们创建了一个 TestA 和一个 TestB 的对象,但在整个 main 函数都运行完后,都没看到两个对象被析构,这是为什么呢?


原来,智能指针 ptr_a 中引用了 ptr_b,同样 ptr_b 中也引用了 ptr_a,在 main 函数退出前,ptr_a 和 ptr_b 的引用计数均为 2,退出 main 函数后,引用计数均变为 1,也就是相互引用。


这等效于说:


  • ptr_a 对 ptr_b 说,哎,我说 ptr_b,我现在的条件是,你先释放我,我才能释放你,这是天生的,造物者决定的,改不了;

  • ptr_b 也对 ptr_a 说,我的条件也是一样,你先释放我,我才能释放你,怎么办?


是吧,大家都没错,相互引用导致的问题就是释放条件的冲突,最终也可能导致内存泄漏。


1.2.2 std::weak_ptr 如何解决相互引用的问题


我们在上面的代码基础上使用 std::weak_ptr 进行修改,如下:


#include <iostream>#include <memory>class TestB;class TestA{public:    TestA()    {        std::cout << "TestA()" << std::endl;    }    void ReferTestB(std::shared_ptr<TestB> test_ptr)    {        m_TestB_Ptr = test_ptr;    }    void TestWork()    {        std::cout << "~TestA::TestWork()" << std::endl;    }    ~TestA()    {        std::cout << "~TestA()" << std::endl;    }private:    std::weak_ptr<TestB> m_TestB_Ptr;};class TestB{public:    TestB()    {        std::cout << "TestB()" << std::endl;    }    void ReferTestB(std::shared_ptr<TestA> test_ptr)    {        m_TestA_Ptr = test_ptr;    }    void TestWork()    {        std::cout << "~TestB::TestWork()" << std::endl;    }    ~TestB()    {  ////把std::weak_ptr类型转换成std::shared_ptr类型        std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock();        tmp->TestWork();        std::cout << "2 ref a:" << tmp.use_count() << std::endl;        std::cout << "~TestB()" << std::endl;    }    std::weak_ptr<TestA> m_TestA_Ptr;};int main(){    std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();    std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();    ptr_a->ReferTestB(ptr_b);    ptr_b->ReferTestB(ptr_a);    std::cout << "1 ref a:" << ptr_a.use_count() << std::endl;    std::cout << "1 ref b:" << ptr_a.use_count() << std::endl;    return 0;}
复制代码


运行结果:


TestA()TestB()1 ref a:11 ref b:1~TestA::TestWork()2 ref a:2~TestB()~TestA()
复制代码


由以上代码运行结果我们可以看到:


  • 所有的对象最后都能正常释放,不会存在上一个例子中的内存没有释放的问题;

  • ptr_a 和 ptr_b 在 main 函数中退出前,引用计数均为 1,也就是说,在 TestA 和 TestB 中对 std::weak_ptr 的相互引用,不会导致计数的增加。在 TestB 析构函数中,调用 std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock(),把 std::weak_ptr 类型转换成 std::shared_ptr 类型,然后对 TestA 对象进行调用。


1.2.3 std::weak_ptr 支持的调用


weak_ptr<T> w;    //空weak_ptr可以指向类型为T的对象weak_ptr<T> w(shared_ptr sp);    //与sp指向相同对象的weak_ptr, T必须能转换为sp指向的类型w = p;    //p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象w.reset();    //weak_ptr置为空w.use_count();    //与w共享对象的shared_ptr的计数w.expired();    //w.use_count()为0则返回true,否则返回falsew.lock();    //w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
复制代码


1.3 std::unique_ptr


uniqut_ptr 是一种对资源具有排他性拥有权的智能指针,即一个对象资源只能同时被一个 unique_ptr 指向。


1.3.1 初始化方式


  • 使用 new


T *pT = new T();std::unique_ptr<T> up1(pT);
复制代码


  • 通过 make_unique


auto pT = make_unique<T>();
复制代码


  • 通过 move()函数


//up也是一个std::unique_ptr<T>指针unique_ptr<T> up1 = std::move(up);
复制代码


1.3.2 unique_ptr 不能被复制或者拷贝


unique_ptr<T> up(new T()); //okunique_ptr<T> up1(up); //error, can not be copyunique_ptr<T> up2 = up; //error, can not be assigned
复制代码


1.3.3 unique_ptr 可以移动赋值或者移动拷贝


unique_ptr<T> pT(new T());unique_ptr<T> pT2 = std::move(pT);    //移动赋值,此时pT被销毁,为空unique_ptr<T> pT3(std::move(pt2)); //移动拷贝,此时pT2被销毁,为空
复制代码


1.3.4 unique_ptr 可以作为函数的返回值


unique_ptr<T> GetPtr(); //function getthe unique pointerunique_ptr<T> pT = GetPtr(); // ok
复制代码


1.3.5 使用范例


#include <iostream>int main(){ std::unique_ptr<int> pInt; pInt.reset(new int()); int *p = pInt.release(); //释放所有权 //由于unique_ptr有std::unique_ptr<T[]>的重载函数,所以它可以用来管理数组资源 std::unique_ptr<int[]> pArray(new int[3]{1,3,3}); }
复制代码


2. 智能指针小结


可以看出,智能指针其实是 std::shared_ptr 和 std::unique_ptr, std::shared_ptr 可以有多个引用对象,但不能互相引用,而 std::unique_ptr 只能有一个引用,不能赋值或者拷贝,但可以移动赋值和移动拷贝,std::weak_ptr 实际上是对 std::shared_ptr 的补充,它并不能对对象进行具体的操作。


注意:shared_ptr,weak_ptr,unique_ptr 这些都是模板,并不是指针或者其他的。


【文章福利】整理了一些最新的 LinuxC/C++服务器开发/架构师面试题、学习资料、教学视频和学习路线脑图(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享有需要的可以自行添加学习交流群960994558进群领取!~





用户头像

赖猫

关注

还未添加个人签名 2020.11.28 加入

纸上得来终觉浅,绝知此事要躬行

评论

发布
暂无评论
c++11&14-智能指针