码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • c++面试三 -- 智能指针--7000字


    一、智能指针

            C++ 中的智能指针是一种用于管理动态分配的内存的对象,它们可以自动进行内存管理,避免内存泄漏和悬挂指针等问题。

    1. 悬挂指针

            悬挂指针(dangling pointer)是指在程序中仍然存在但已经不再指向有效内存地址的指针。悬挂指针通常是由于以下情况引起的:

            1. 释放内存后未将指针置空:

            当使用 delete 或 free 等方法释放了指针指向的内存后,如果未将指针置空(即将指针设置为 nullptr 或 NULL),则该指针仍然保留之前的内存地址,成为悬挂指针。

    1. int* ptr = new int(42);
    2. delete ptr;
    3. // ptr现在成为悬挂指针,它指向的内存已经被释放
            2. 超出作用域的引用:

            当指针指向的对象超出了其作用域(例如指向了一个局部变量),并且该对象的内存被释放,那么指针就会成为悬挂指针。

    1. int* danglingPtr;
    2. {
    3. int value = 42;
    4. danglingPtr = &value;
    5. } // value的作用域结束,danglingPtr成为悬挂指针

            3. 指向已经被销毁的对象:

            如果指针指向的对象在其生命周期内被销毁了,那么指针就成为悬挂指针。

    1. int* danglingPtr;
    2. {
    3. std::unique_ptr<int> ptr = std::make_unique<int>(42);
    4. danglingPtr = ptr.get();
    5. } // ptr超出作用域,其指向的对象被销毁,danglingPtr成为悬挂指针

    2.野指针

            野指针是指指针指向的内存地址是随机的、未初始化的,或者指向的内存区域未经过分配。野指针可能是在创建指针后没有给它赋值(即未初始化指针),或者是在释放内存后未将指针置空,但继续使用该指针。

            野指针指向的内存地址通常是随机的,因此访问野指针可能会导致程序崩溃、数据损坏或安全漏洞。

            悬挂指针是指针仍然保留着之前指向的地址,但该地址已经不再有效;而野指针是指针指向的地址是随机的或未经过初始化。在编程中,应该尽量避免出现悬挂指针和野指针。

    3.智能指针

    C++现代实用教程:智能指针_哔哩哔哩_bilibili

     

    3.1 unique_ptr指针 
            3.1.1 特点:

     3.1.2 创建

    栈上:出作用域之后会自动调用析构函数

    1. //stack
    2. Cat c1("OK");
    3. c1.cat_info();
    4. {
    5. Cat c1("OK");
    6. c1.cat_info();
    7. }

    堆上:需要手动delete释放(不安全)

    1. Cat *c_p1 = new Cat("yy");
    2. int *i_p1 = new int(100);
    3. c_p1->cat_info();
    4. {
    5. int *i_p1 = new int(200); // 重新声明,但是是在局部作用域中,与外部不同,结果为100。需要delete两次
    6. Cat *c_p1 new Cat("yy_scope");
    7. c_p1->cat_info();
    8. delete c_p1;
    9. delete i_pi;
    10. }
    11. delete c_p1;
    12. delete i_pi;
    1. Cat *c_p1 = new Cat("yy");
    2. int *i_p1 = new int(100);
    3. c_p1->cat_info();
    4. {
    5. i_p1 = new int(200); // 不重新声明,只是修改了值,结果为200 ,只用delete一次
    6. Cat *c_p1 = new Cat("yy_scope");
    7. c_p1->cat_info();
    8. delete c_p1;
    9. delete i_pi;
    10. }
    11. delete c_p1;

    unique_ptr创建的三种方式:

    1. 通过原始指针创建

    1. Cat *c_p2 =new Cat("yz");
    2. std::unique_ptr u_c_p2(c_p2};
    3. // 此时原始指针还能用,需要进行销毁,否则不满足独占指针要求,否则如下
    4. //c_p2->cat_info();
    5. //u_c_p2->cat_info();
    6. //c_p2->set_cat_name("ok");
    7. //u_c_p2->cat_info();
    8. // 销毁
    9. c_p2 = nullptr;
    10. c_p2 =nullptr;
    11. u_c_p2->cat_info();

    2. 使用new创建

    1. std::unique_ptr u_c_p3{new Cat("dd")};
    2. u_c_p3->cat_info();
    3. u_c_p3->set_cat_name("oo");
    4. u_c_p3->cat_info();

     3.使用std::make_unique

    1. std::unique_ptr u_c_p4 =make_unique();
    2. u_c_p4->cat_info();
    3. u_c_p4->set_cat_name("po");
    4. u_c_p4->cat_info();

    4. 移动语义创建

    可以通过使用移动语义将一个已有的 std::unique_ptr 赋值给另一个 std::unique_ptr。

    1. #include
    2. std::unique_ptr<int> u_c_p5 = std::move(u_c_p4 );
    3.1.3  get()和常量类型
    1. std::unique_ptr<int> u_i_p4 =make_unique<int>(200);
    2. cout << "int address" << u_i_p4 .get() << endl; //get 获取原始指针或者地址
    3. cout<< * u_i_p4<// 打印值
    3.2 unique_ptr和函数调用    (资源的所属权问题)

    3.2.1 Passing by value  通过值传递
    1. void do_with_cat_pass_value(std::unique_ptr c){
    2. c->cat_info();
    3. }
    4. int main(){
    5. std::unique_ptr c1 = make_unique("ff");
    6. // 1. 使用 std::move 转移资源的所有权给函数,此时c1不再拥有资源的所有权了
    7. do_with_cat_pass_value(std::move(c1));
    8. // 2. 直接将参数传入make_unique ,将自动转换成move
    9. do_with_cat_pass_value(std::make_unique()); // move
    10. }
    3.2.2 Passing by reference  通过引用传递,可以修改值
    1. void do_with_cat_pass_ref(std::unique_ptr &c){
    2. c->set_cat_name("oo");
    3. c->cat_info();
    4. c.reset(); // 释放先前所拥有的对象,不再指向任何对象
    5. }
    6. int main(){
    7. std::unique_ptr c2 = make_unique("ff");
    8. // 不用使用move,直接c2
    9. do_with_cat_pass_value(c2);
    10. }
    1. void do_with_cat_pass_ref(const std::unique_ptr &c){
    2. c->set_cat_name("oo");
    3. c->cat_info();
    4. // c.reset(); // 释放先前所拥有的对象,不再指向任何对象 不能使用了
    5. }
    6. int main(){
    7. std::unique_ptr c2 = make_unique("ff");
    8. // 不用使用move,直接c2
    9. do_with_cat_pass_value(c2);
    10. c2 ->cat->info();
    11. }

    3.3.3  Return by Value
    1. std::unique_ptr get_unique_ptr(){
    2. std::unique_ptr p_dog = std:: make_unique("Local cat");
    3. cout <get()<
    4. cout <<&p_dog <
    5. return p_dog;
    6. }
    7. // 链式
    8. get_unique_ptr->cat_info();
    1. p_dog.get(): 这个表达式返回指向 std::unique_ptr 管理的对象的原始指针。get() 函数是 std::unique_ptr 类的成员函数,它返回一个指向被管理对象的原始指针。使用 get() 可以获取 std::unique_ptr 所拥有的对象的原始指针,但是请注意,这个原始指针不包含所有权信息,因此需要谨慎使用,特别是不要手动释放内存。

    2. &p_dog: 这个表达式返回的是指向 std::unique_ptr 对象本身的指针,即指向 std::unique_ptr 对象的地址。& 是取地址运算符,它返回变量的地址。std::unique_ptr 是一个对象,因此 &p_dog 返回的是指向 std::unique_ptr 对象的指针

    3.3 shared_ptr 计数指针/共享指针

            在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:

            有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
            有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
            没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。
            智能指针shared_ptr 是存储动态创建对象的指针,其主要功能是管理动态创建对象的销毁,从而帮助彻底消除内存泄漏和悬空指针的问题。

    shared_ptr的原理和特点
            基本原理:就是记录对象被引用的次数,当引用次数为 0 的时候,也就是最后一个指向该对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。

            特点:它所指向的资源具有共享性,即多个shared_ptr可以指向同一份资源,并在内部使用引用计数机制来实现这一点。

            共享指针内存:每个 shared_ptr 对象在内部指向两个内存位置:

            指向对象的指针;
    用于控制引用计数数据的指针。
            1.当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。

            2.当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

            shared_ptr像普通指针一样使用,可以将*和->与 shared_ptr 对象一起使用,也可以像其他 shared_ptr 对象一样进行比较;

    3.3.1 常量类型
    1. int main(){
    2. std::shared_ptr<int> i_p_1 =make_shared<int>(10);
    3. // std::shared_ptr i_p_2 =make_shared{new int(10)};
    4. // copy
    5. std::shared_ptr<int> i_p_2 =i_p_1;
    6. cout<< "use cout :" << *i_p_1.use_cout() <// 1
    7. cout<< "use cout :" << *i_p_2.use_cout() <// 1
    8. // change 两个指针指向同一个内存
    9. *i_p_2 =30;
    10. cout<< ":" << *i_p_1 <// 30
    11. cout<< ":" << *i_p_2 <// 30
    12. // 将i_p_2置为nullptr
    13. i_p_2=nullptr
    14. cout<< "use cout :" << *i_p_1.use_cout() <// 1
    15. cout<< "use cout :" << *i_p_2.use_cout() <// 0
    16. // 将i_p_1置为nullptr
    17. std::shared_ptr<int> i_p_3 =i_p_1;
    18. i_p_1=nullptr
    19. cout<< "use cout :" << *i_p_1.use_cout() <// 0
    20. cout<< "use cout :" << *i_p_2.use_cout() <// 2
    21. cout<< "use cout :" << *i_p_3.use_cout() <// 2
    22. cout<< "value:" << *i_p_1 <
    23. cout<< "use cout :" << *i_p_1.use_cout() <
    24. return 0;
    25. }
    3.3.2 自定义类型
    1. // 自定义类型
    2. std::shared_ptr c_p_1 =make_shared();
    3. cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;
    4. std::shared_ptr c_p_2 =c_p_1;
    5. std::shared_ptr c_p_3 =c_p_1;
    6. cout<< "c_p_1 use cout :" << c_p_1.use_count() << endl;
    7. cout<< "c_p_2 use cout :" << c_p_2 .use_count() << endl;
    8. cout<< "c_p_3 use cout :" << c_p_3 .use_count() << endl;
    3.3.3 make_shared的构建方法

          (1).构造函数创建

    1. 1.shared_ptr ptr;//ptr 的意义就相当于一个 NULL 指针
    2. 2.shared_ptr ptr(new T());//从new操作符的返回值构造
    3. 3.shared_ptr ptr2(ptr1); // 使用拷贝构造函数的方法,会让引用计数加 1
    4. //shared_ptr 可以当作函数的参数传递,或者当作函数的返回值返回,这个时候其实也相当于使用拷贝构造函数。
    5. 4./*假设B是A的子类*/
    6. shared_ptr ptrb(new B());
    7. shared_ptr ptra( dynamic_pointer_cast(ptrb) );//从 shared_ptr 提供的类型转换 (cast) 函数的返回值构造
    8. 5./* shared_ptr 的“赋值”*/
    9. shared_ptr a(new T());
    10. shared_ptr b(new T());
    11. a = b; // 此后 a 原先所指的对象会被销毁,b 所指的对象引用计数加 1
    12. //shared_ptr 也可以直接赋值,但是必须是赋给相同类型的 shared_ptr 对象,而不能是普通的 C 指针或 new 运算符的返回值。
    13. //当共享指针 a 被赋值成 b 的时候,如果 a 原来是 NULL, 那么直接让 a 等于 b 并且让它们指向的东西的引用计数加 1;
    14. // 如果 a 原来也指向某些东西的时候,如果 a 被赋值成 b, 那么原来 a 指向的东西的引用计数被减 1, 而新指向的对象的引用计数加 1。
    15. 6./*已定义的共享指针指向新的new对象————reset()*/
    16. shared_ptr ptr(new T());
    17. ptr.reset(new T()); // 原来所指的对象会被销毁

            (2)make_shared辅助函数创建

    std::shared_ptr<int> foo = std::make_shared<int> (10);
    3.3.4 shared_ptr 与函数

    1. void cat_by_value( std::shared_ptr cat){
    2. cout << "cat use coout "<< cat.use_cout() << endl; // 2
    3. }
    4. void cat_by_ref( std::shared_ptr &cat){
    5. // cat.reset(new Cat()); // 先创建新对象,将原先cat对象内容覆盖,然后reset
    6. cout << "cat use coout "<< cat.use_cout() << endl; // 2
    7. }
    8. std::shared_ptr get_shared_ptr(){
    9. std::shared_ptr cat_p= std::make_shared("dd);
    10. }
    11. int main(){
    12. std::shared_ptr c1 =make_shared("dd");
    13. cat_by_value(c1);
    14. cout << "c1 use coout "<< c1.use_cout() << endl; // 1
    15. cat_by_ref(c1);
    16. std::shared_ptr c_p =get_shared_ptr();
    17. get_shared_ptr->cat_info();
    18. }
    3.4 shared_ptr 和 unique_ptr转换

    3.5 weak_ptr  弱引用指针

      std::weak_ptr 是 C++11 引入的一个智能指针类,用于解决 std::shared_ptr 的循环引用问题。它是一个弱引用指针,不会增加指向对象的引用计数,也不会拥有对象的所有权,因此不会影响对象的生命周期。

    std::weak_ptr 主要用于解决以下两个问题:

    1. 循环引用问题:当两个或多个对象相互持有对方的 std::shared_ptr,就会形成循环引用,导致对象无法被正确释放,从而产生内存泄漏。使用 std::weak_ptr 可以打破循环引用,避免内存泄漏的发生。

    2. 避免悬挂指针:当对象的 std::shared_ptr 被释放后,指向该对象的 std::weak_ptr 仍然可以继续存在,但是无法访问对象。因此,使用 std::weak_ptr 可以避免悬挂指针的出现,从而提高程序的稳定性。

            使用 std::weak_ptr 需要配合 std::shared_ptr 使用,通过 std::shared_ptr 对象的 weak_ptr 方法来创建 std::weak_ptr 对象。std::weak_ptr 可以通过 lock 方法获取一个有效的 std::shared_ptr 对象,用于访问所指向的对象,但是需要注意,获取的 std::shared_ptr 可能为空,需要进行有效性检查。

    // 产生循环依赖问题 

     

    // 使用weak_ptr 

  • 相关阅读:
    [Java基础] 设计模式之策略模式
    11、视频分类建议
    用html写一个爱心
    Python每日一练——第44天:大厂真题练习
    JavaScript 56 JavaScript 调试
    redux一步一步的使用
    DxO PhotoLab 6 for Mac/Win:专业RAW图片编辑的利器
    LeetCode LCR024.反转链表 经典题目 C写法
    SpringBoot 优雅地实现文件的上传和下载
    软考高级信息系统项目管理师系列论文十八:论信息系统项目沟通管理
  • 原文地址:https://blog.csdn.net/leimeili/article/details/136345555
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号