• 智能指针学习笔记


    目录

    1 智能指针简介

    关于智能指针

    概述

    2 独占指针:unique_ptr

    3 unique_ptr与函数调用

    4 计数指针 share_ptr

    5 shared_ptr与函数

    6 shared_ptr与unique_ptr

    7 weak_ptr


    1 智能指针简介

    关于智能指针

    • 在C++ 11中引入智能指针的概念,使得C++程序员不需要手动释放内存
    • 智能指针的分类:unique_ptr, shared_ptr, weak_ptr 
    • 注意:auto_ptr已经被抛弃

    概述

    C++指针包含两种

    1. 原始指针
    2. 智能指针:对原始指针的封装,其优点是自动分配内存,不用担心潜在的内存泄露。

    智能指针和原始指针的关系

    • 并不是所有的指针可以封装为智能指针,很多时候原始指针更方便

    • 各种指针中,最常用的是原始指针,其次是unique_ptr和shared_ptr

    • weak_ptr是share_ptr的一种补充,应用场景少

    接下来思考一个问题,为什么有了智能指针,还需要rust呢?

    • 因为智能指针只解决了一部分问题,即独占/共享所有权指针的释放和传输
    • 智能指针没有从根本上解决C++内存安全的问题,不加以注意依然造成内存安全问题,而rust解决的问题更广泛一些。

    2 独占指针:unique_ptr

    特点:

    • 在任何给定的时刻,只能有一个指针管理内存
    • 当指针超出作用域时,内存将自动释放
    • 该类型指针不可copy,只可以move

    三种创建方式:

    • 通过已有指针创建:需要将指针置为空,并销毁,不然不满足独占作用
    • 通过new创建:同上
    • 通过make_unique创建:推荐使用

    unique_ptr可以通过get()获取地址,另外可以通过->调用成员函数,通过*进行解引用。

    1. //cat.h
    2. #ifndef CAT_H
    3. #define CAT_H
    4. #include
    5. #include
    6. #include "cat.h"
    7. class Cat
    8. {
    9. public:
    10. Cat(std::string name);
    11. Cat() = default;
    12. ~Cat();
    13. void cat_info() const
    14. {
    15. std::cout << "cat info name: " << name << std::endl;
    16. }
    17. std::string get_name() const
    18. {
    19. return name;
    20. }
    21. void set_cat_name(const std::string& name)
    22. {
    23. this->name = name;
    24. }
    25. private:
    26. std::string name{ "Mini" };
    27. };
    28. #endif
    1. //cat.cpp
    2. #include "cat.h"
    3. Cat::Cat(std::string name) : name(name)
    4. {
    5. std::cout << "Constructor of Cat :" << name << std::endl;
    6. }
    7. Cat::~Cat()
    8. {
    9. std::cout << "Destructor of Cat :" << name << std::endl;
    10. }
    1. //main.cpp
    2. #include
    3. #include
    4. #include "cat.h"
    5. #include "cat.cpp"
    6. using namespace std;
    7. int main(int argc, char *argv[])
    8. {
    9. Cat c1("OK");
    10. c1.cat_info();
    11. {
    12. Cat c1("OK");
    13. c1.cat_info();
    14. }
    15. cout << "-------------yz----------" << endl;
    16. return 0;
    17. }

     

    1. //没有delete由new创建的指针
    2. #include
    3. #include
    4. #inlclude"cat.h"
    5. using namespace std;
    6. int main()
    7. {
    8. Cat *cp1 = new Cat("");
    9. cp1->cat_info();
    10. {
    11. Cat *cp1 = new Cat("yy");
    12. cp1->cat_info();
    13. }
    14. cout<< "-------------yz----------";
    15. return 0;
    16. }

    以上代码是有问题的,我们没有去释放申请的内存,是不会调用析构函数,内存得不到释放。另外当我们delete两次指针的时候也会出现错误。

    因此引入unique_ptr智能指针解决上述问题

    代码实现如下

    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. int main(int argc, char *argv[])
    7. {
    8. //创建unique_ptr方式1
    9. Cat *cp1 = new Cat("yz");
    10. std::unique_ptr u_cp1(cp1);
    11. u_cp1->cat_info();
    12. delete cp1;
    13. cp1 = nullptr;
    14. cout << "-------------yz----------" << endl;
    15. //创建unique_ptr方式2
    16. std::unique_ptr u_cp2(new Cat("gg"));
    17. u_cp2->cat_info();
    18. cout << "-------------yz----------" << endl;
    19. //创建unique_ptr方式3
    20. std::unique_ptr u_cp3 = make_unique();
    21. u_cp3->cat_info();
    22. return 0;
    23. }

    但是需要注意的是,方式一好像在最新的g++编译器上不需要delete和置空操作。如下为我的上述代码的输出。

    1. Constructor of Cat :yz
    2. cat info name: yz
    3. Destructor of Cat :yz
    4. -------------yz----------
    5. Constructor of Cat :gg
    6. cat info name: gg
    7. -------------yz----------
    8. cat info name: Mini
    9. Destructor of Cat :Mini
    10. Destructor of Cat :gg
    11. Destructor of Cat :free(): double free detected in tcache 2
    12. Aborted (core dumped)

    如果我将delete操作和置空操作删除,例如

    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. int main(int argc, char *argv[])
    7. {
    8. //创建unique_ptr方式1
    9. Cat *cp1 = new Cat("yz");
    10. std::unique_ptr u_cp1(cp1);
    11. u_cp1->cat_info();
    12. cout << "-------------yz----------" << endl;
    13. //创建unique_ptr方式2
    14. std::unique_ptr u_cp2(new Cat("gg"));
    15. u_cp2->cat_info();
    16. cout << "-------------yz----------" << endl;
    17. //创建unique_ptr方式3
    18. std::unique_ptr u_cp3 = make_unique();
    19. u_cp3->cat_info();
    20. return 0;
    21. }

    输出为

    1. Constructor of Cat :yz
    2. cat info name: yz
    3. -------------yz----------
    4. Constructor of Cat :gg
    5. cat info name: gg
    6. -------------yz----------
    7. cat info name: Mini
    8. Destructor of Cat :Mini
    9. Destructor of Cat :gg
    10. Destructor of Cat :yz

    下面介绍解引用和取地址操作,以方式为例

    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. int main(int argc, char *argv[])
    7. {
    8. //创建unique_ptr方式3
    9. std::unique_ptr<int> u_cp3 = make_unique<int>(200);
    10. cout<<"* u_cp3 = "<<* u_cp3<
    11. cout<<"get adress"<get()<
    12. return 0;
    13. }

    输出为

    1. * u_cp3 = 200
    2. get adress0x55e978458e70

    3 unique_ptr与函数调用

    • unique_ptr是不可Copy,只可以Move
    • 在做函数参数或返回值中一定要注意所有权

    注意事项:

    Passing by value

    • 需要注意std::move来转移内存拥有权
    • 如果参数之间传入std:make_unique语句 自动转换为move
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. void Passing_by_value( std::unique_ptr c){
    7. c->cat_info();
    8. }
    9. int main(int argc, char *argv[])
    10. {
    11. //创建unique_ptr方式3
    12. std::unique_ptr u_cp1 = make_unique("haha");
    13. Passing_by_value(std::move(u_cp1));//需要注意std::move来转移内存拥有权,否则报错。另外move后也不能再使用u_cp1
    14. Passing_by_value(std::make_unique("xixi"));
    15. return 0;
    16. }

    从下面输出结果看到,在move操作转移所有权后会调用Passing_by_value函数和析构函数,说明智能指针不再可用。

    1. Constructor of Cat :haha
    2. cat info name: haha
    3. Destructor of Cat :haha
    4. Constructor of Cat :xixi
    5. cat info name: xixi
    6. Destructor of Cat :xixi

    Passing by reference

    • 如果设置参数为const则不能改变指向,比如说reset()
    • reset()方法为智能指针清空方法。当传常引用时,不可以用reset()清空智能指针。
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. void Passing_by_ref( const std::unique_ptr &c){
    7. c->set_cat_name("ee");
    8. c->cat_info();
    9. }
    10. int main(int argc, char *argv[])
    11. {
    12. //创建unique_ptr方式3
    13. std::unique_ptr u_cp1 = make_unique("haha");
    14. Passing_by_ref(u_cp1);//需要注意std::move来转移内存拥有权,否则报错。另外move后也不能再使用u_cp1
    15. u_cp1->cat_info();
    16. return 0;
    17. }

    由下列输出结果可得,传引用可以修改变量值。

    1. Constructor of Cat :haha
    2. cat info name: ee
    3. cat info name: ee
    4. Destructor of Cat :ee

    Return by value

    • 指向一个local object
    • 可以作为链式函数
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. std::unique_ptr get_unique_ptr()//return by value
    7. {
    8. std::unique_ptr p_dog = std::make_unique("dog");
    9. cout<<"unique_ptr adresss"<get()<
    10. cout<<"unique_ptr adresss"<<&p_dog<
    11. return p_dog;
    12. }
    13. int main(int argc, char *argv[])
    14. {
    15. get_unique_ptr()->cat_info();//链式调用
    16. return 0;
    17. }

    输出为

    1. Constructor of Cat :dog
    2. unique_ptr adresss0x55a6558e1e70
    3. unique_ptr adresss0x7ffeb6c9ae70
    4. cat info name: dog
    5. Destructor of Cat :dog

    4 计数指针 share_ptr

    • 计数指针 share_ptr又称为共享指针
    • 与unique_ptr不同的是可以共享数据
    • share_ptr创建了一个计数器与类对象所指的内存相关联
    • Copy则计数加1,销毁则减1
    • api为use_count(),查询引用计数
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. int main(int argc, char *argv[])
    7. {
    8. std::shared_ptr p1 = make_shared("aa");
    9. p1->cat_info();
    10. cout<<"p1 count: "<use_count()<
    11. std::shared_ptr p2 = p1;
    12. cout<<"p1 count: "<use_count()<
    13. cout<<"p2 count: "<use_count()<
    14. p1=nullptr;
    15. cout<<"p1 count: "<use_count()<
    16. cout<<"p2 count: "<use_count()<
    17. return 0;
    18. }

     输出为

    1. Constructor of Cat :aa
    2. cat info name: aa
    3. p1 count: 1
    4. p1 count: 2
    5. p2 count: 2
    6. p1 count: 0
    7. p2 count: 1
    8. Destructor of Cat :aa

    值得注意的是,无论引用计数为多少,数据只有一套。无论引用计数为多少,程序运行结束时只会调用一次析构函数。

    5 shared_ptr与函数

    • shared_ptr passed by value :copy时引用计数加1
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. void pass_by_value(std::shared_ptr c){
    7. c->cat_info();
    8. cout<<"count: "<use_count()<
    9. }
    10. int main(int argc, char *argv[])
    11. {
    12. std::shared_ptr p1 = make_shared("aa");
    13. p1->cat_info();
    14. pass_by_value(p1);
    15. cout<<"p1 count: "<use_count()<
    16. return 0;
    17. }

    由下面可得,当进入函数体时引用计数加1,离开函数体减1。

    1. Constructor of Cat :aa
    2. cat info name: aa
    3. cat info name: aa
    4. count: 2
    5. p1 count: 1
    6. Destructor of Cat :aa

    • shared_ptr passed by ref:const表示不可以改变指向

    类似与unique_ptr

    • return by value:链式调用

    l类似与unique_ptr

    6 shared_ptr与unique_ptr

    • 不能将shared_ptr转换为unique_ptr
    • unique_ptr可以转换为shared_ptr:通过std::move转换
    • 将你的函数返回unique_ptr是一种常见的设计模式,这样可以提高代码的复用性,你可以随时改变为shared_ptr
    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. std::unique_ptr pass_by_value(){
    7. std::unique_ptr c = make_unique("cc");
    8. return c;
    9. }
    10. int main(int argc, char *argv[])
    11. {
    12. std::unique_ptr p0 = make_unique("bb");
    13. std::shared_ptr p1 = std::move(p0);//unique_ptr转换为shared_ptr
    14. p1->cat_info();
    15. cout<<"p1 count: "<use_count()<
    16. std::shared_ptr p3 = pass_by_value();//直接用shared_ptr接收unique_ptr
    17. cout<<"p3 count"<use_count()<
    18. return 0;
    19. }

    输出为

    1. Constructor of Cat :bb
    2. cat info name: bb
    3. p1 count: 1
    4. Constructor of Cat :cc
    5. p3 count1
    6. Destructor of Cat :cc
    7. Destructor of Cat :bb

    7 weak_ptr

    • weak_ptr并不拥有所有权
    • 不能调用->和解引用*

    weak_ptr为什么存在呢?

    A类中有一个需求需要存储其他A类对象的信息,如果使用shared_ptr,那么在销毁时会遇到循环依赖问题,所以我们这里需要用一个不需要拥有权的指针来标记同类对象。

    weak_ptr可以通过lock()函数来提升为shared_ptr(类型转换)

    例1

    1. #include
    2. #include
    3. #include "cat.h"
    4. #include "cat.cpp"
    5. using namespace std;
    6. int main(int argc, char *argv[])
    7. {
    8. std::shared_ptr p0 = make_shared("bb");
    9. std::weak_ptr p1 = p0;
    10. cout<<"p0 use_count: "<use_count()<
    11. std::shared_ptr p2 = p1.lock();
    12. cout<<"p0 use_count: "<use_count()<
    13. return 0;
    14. }

    输出如下,可以看出weak_ptr不会增加引用计数,将weak_ptr使用lock()函数转换后可以增加引用计数

    1. Constructor of Cat :bb
    2. p0 use_count: 1
    3. p0 use_count: 2
    4. Destructor of Cat :bb

    解决循环依赖问题

    更改cat.h代码如下

    1. #ifndef CAT_H
    2. #define CAT_H
    3. #include
    4. #include
    5. #include "cat.h"
    6. #include
    7. class Cat
    8. {
    9. public:
    10. Cat(std::string name);
    11. Cat() = default;
    12. ~Cat();
    13. void cat_info() const
    14. {
    15. std::cout << "cat info name: " << name << std::endl;
    16. }
    17. std::string get_name() const
    18. {
    19. return name;
    20. }
    21. void set_cat_name(const std::string& name)
    22. {
    23. this->name = name;
    24. }
    25. void set_friend(std::shared_ptr c){
    26. m_friend = c;
    27. }
    28. private:
    29. std::string name{ "Mini" };
    30. std::shared_ptr m_friend;
    31. };
    32. #endif

     此时就需要用weak_ptr来解决上图中的问题了,我们需要修改cat.h代码,如下图

     此时再去运行main函数,就发现问题已经解决了

     

  • 相关阅读:
    【计算机组成原理】指令系统(一)—— 指令格式
    Flutter | Stack 超出部分不响应手势的独特解决方案
    CPU受限直接执行
    基于Sim2Real的鸟瞰图语义分割方法
    【Ascend C算子开发(入门)】——Ascend C编程模式与范式
    计算机视觉全系列实战教程:(八)图像变换-点运算、灰度变换、直方图变换
    Java:org.apache.commons.io包的工具类:IOUtils、FileUtils、FilenameUtils
    springboot+websocket聊天室(私聊+群聊)
    艾美捷ProSci丨ProSci TM4SF1 抗体解决方案
    3线硬件SPI+DMA驱动 HX8347 TFT屏
  • 原文地址:https://blog.csdn.net/weixin_44387095/article/details/126122486