当前位置:   article > 正文

智能指针学习笔记_智能指针置空

智能指针置空

目录

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 <string>
  5. #include <iostream>
  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<iostream>
  3. #include<memory>
  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<iostream>
  3. #include<memory>
  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<iostream>
  2. #include<memory>
  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<Cat> 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<Cat> u_cp2(new Cat("gg"));
  17. u_cp2->cat_info();
  18. cout << "-------------yz----------" << endl;
  19. //创建unique_ptr方式3
  20. std::unique_ptr<Cat> u_cp3 = make_unique<Cat>();
  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<iostream>
  2. #include<memory>
  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<Cat> u_cp1(cp1);
  11. u_cp1->cat_info();
  12. cout << "-------------yz----------" << endl;
  13. //创建unique_ptr方式2
  14. std::unique_ptr<Cat> u_cp2(new Cat("gg"));
  15. u_cp2->cat_info();
  16. cout << "-------------yz----------" << endl;
  17. //创建unique_ptr方式3
  18. std::unique_ptr<Cat> u_cp3 = make_unique<Cat>();
  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<iostream>
  2. #include<memory>
  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<<endl;
  11. cout<<"get adress"<<u_cp3.get()<<endl;
  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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. void Passing_by_value( std::unique_ptr<Cat> c){
  7. c->cat_info();
  8. }
  9. int main(int argc, char *argv[])
  10. {
  11. //创建unique_ptr方式3
  12. std::unique_ptr<Cat> u_cp1 = make_unique<Cat>("haha");
  13. Passing_by_value(std::move(u_cp1));//需要注意std::move来转移内存拥有权,否则报错。另外move后也不能再使用u_cp1
  14. Passing_by_value(std::make_unique<Cat>("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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. void Passing_by_ref( const std::unique_ptr<Cat> &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<Cat> u_cp1 = make_unique<Cat>("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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. std::unique_ptr<Cat> get_unique_ptr()//return by value
  7. {
  8. std::unique_ptr<Cat> p_dog = std::make_unique<Cat>("dog");
  9. cout<<"unique_ptr adresss"<<p_dog.get()<<endl;
  10. cout<<"unique_ptr adresss"<<&p_dog<<endl;
  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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. int main(int argc, char *argv[])
  7. {
  8. std::shared_ptr<Cat> p1 = make_shared<Cat>("aa");
  9. p1->cat_info();
  10. cout<<"p1 count: "<<p1.use_count()<<endl;
  11. std::shared_ptr<Cat> p2 = p1;
  12. cout<<"p1 count: "<<p1.use_count()<<endl;
  13. cout<<"p2 count: "<<p2.use_count()<<endl;
  14. p1=nullptr;
  15. cout<<"p1 count: "<<p1.use_count()<<endl;
  16. cout<<"p2 count: "<<p2.use_count()<<endl;
  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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. void pass_by_value(std::shared_ptr<Cat> c){
  7. c->cat_info();
  8. cout<<"count: "<<c.use_count()<<endl;
  9. }
  10. int main(int argc, char *argv[])
  11. {
  12. std::shared_ptr<Cat> p1 = make_shared<Cat>("aa");
  13. p1->cat_info();
  14. pass_by_value(p1);
  15. cout<<"p1 count: "<<p1.use_count()<<endl;
  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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. std::unique_ptr<Cat> pass_by_value(){
  7. std::unique_ptr<Cat> c = make_unique<Cat>("cc");
  8. return c;
  9. }
  10. int main(int argc, char *argv[])
  11. {
  12. std::unique_ptr<Cat> p0 = make_unique<Cat>("bb");
  13. std::shared_ptr<Cat> p1 = std::move(p0);//unique_ptr转换为shared_ptr
  14. p1->cat_info();
  15. cout<<"p1 count: "<<p1.use_count()<<endl;
  16. std::shared_ptr<Cat> p3 = pass_by_value();//直接用shared_ptr接收unique_ptr
  17. cout<<"p3 count"<<p3.use_count()<<endl;
  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<iostream>
  2. #include<memory>
  3. #include "cat.h"
  4. #include "cat.cpp"
  5. using namespace std;
  6. int main(int argc, char *argv[])
  7. {
  8. std::shared_ptr<Cat> p0 = make_shared<Cat>("bb");
  9. std::weak_ptr<Cat> p1 = p0;
  10. cout<<"p0 use_count: "<<p0.use_count()<<endl;
  11. std::shared_ptr<Cat> p2 = p1.lock();
  12. cout<<"p0 use_count: "<<p0.use_count()<<endl;
  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 <string>
  4. #include <iostream>
  5. #include "cat.h"
  6. #include<memory>
  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<Cat> c){
  26. m_friend = c;
  27. }
  28. private:
  29. std::string name{ "Mini" };
  30. std::shared_ptr<Cat> m_friend;
  31. };
  32. #endif

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

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

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/781601
推荐阅读
相关标签
  

闽ICP备14008679号