• C++智能指针[上]


    1.代码问题与初步解决

    int div()
    {
    	int a, b;
    	cin >> a >> b;
    	if (b == 0)
    		throw invalid_argument("除0错误");
    	return a / b;
    }
    void Func()
    {
    	int* p1 = new int;
    	int* p2 = new int;
    	cout << div() << endl;
    
    	delete p1;
    	delete p2;
    }
    int main()
    {
    	try
    	{
    		Func();
    	}
    	catch (exception& e)
    	{
    		cout << e.what() << endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    很明显 若p2抛异常那么需要释放p1 若div函数抛异常 那么需要释放p1 p2 …怎么改进代码?

    int div()
    {
    	int a, b;
    	cin >> a >> b;
    	if (b == 0)
    		throw invalid_argument("除0错误");
    
    	return a / b;
    }
    
    void Func()
    {
    	int* p1 = new int;
    	int* p2 = nullptr;
    	try
    	{
    		p2 = new int;
    	}
    	catch (...)
    	{
    		delete p1;
    		throw;
    	}
    
    	try
    	{
    		cout << div() << endl;
    	}
    	catch (...)
    	{
    		delete p1;
    		delete p2;
    
    		throw;
    	}
    
    	delete p1;
    	delete p2;
    }
    
    int main()
    {
    	try
    	{
    		Func();
    	}
    	catch (exception& e)
    	{
    		cout << e.what() << endl;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    如果后续还有更多 那么就需要我们一个一个搞 怎么办???

    2.内存泄漏相关知识

    2.1什么是内存泄漏

    内存泄漏:因为疏忽或错误造成程序未能释放已不再使用的内存的情况 内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费

    void MemoryLeaks()
    {
     // 1.内存申请了忘记释放
     int* p1 = (int*)malloc(sizeof(int));
     int* p2 = new int;
     // 2.异常安全问题
     int* p3 = new int[10];
     Func(); // Func函数抛异常导致 delete[] p3未执行  p3没被释放.
     delete[] p3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等,出现内存泄漏会导致响应越来越慢,最终卡死

    2.2内存泄漏分类

    1. 堆内存泄漏(Heap leak)
      堆内存指的是程序执行中通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 释放。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,产生Heap Leak。
    2. 系统资源泄漏
      程序使用系统分配的资源,如方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

    2.3检测内存泄漏

    linux下内存泄漏检测:linux下几款内存泄漏检测工具
    windows下第三方工具:VLD工具说明
    其他工具:内存泄漏工具比较

    2.4如何避免内存泄漏

    1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放
    2. 采用RAII思想或者智能指针来管理资源。
    3. 自己实现自带内存泄漏检测的私有内存管理库
    4. 出问题了使用内存泄漏工具检测

    1、事前预防型。如智能指针。2、事后查错型。如泄漏检测工具。

    3.智能指针

    3.1文档查阅

    智能指针(现代 C++)
    在这里插入图片描述
    RAII (Resource Acquisition Is Initialization)
    在这里插入图片描述
    在这里插入图片描述

    3.2代码初识

    template<class T>
    class SmartPtr
    {
    public:
    	SmartPtr(T* ptr)
    		:_ptr(ptr)
    	{
    	
    	}
    
    	~SmartPtr()
    	{
    		cout << "delete: " << _ptr << endl;
    		delete _ptr;
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    
    int div()
    {
    	int a, b;
    	cin >> a >> b;
    	if (b == 0)
    		throw invalid_argument("除0错误");
    
    	return a / b;
    }
    
    void Func()
    {
    	SmartPtr<int> sp1(new int(1));
    	SmartPtr<int> sp2(new int(2));
    
    	cout << div() << endl;
    
    	*sp1 = 10;
    
    	cout << *sp1 << endl;
    	cout << *sp2 << endl;
    }
    
    int main()
    {
    	try
    	{
    		Func();
    	}
    	catch (exception& e)
    	{
    		cout << e.what() << endl;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    在这里插入图片描述

    p1抛异常没问题 p2抛异常当前函数栈帧结束 p1对象析构 div函数抛异常 同前

    3.3RAII思想

    RAII(Resource Acquisition Is Initialization) : 一种利用对象生命周期来控制程序资源(如内
    存、文件句柄、网络连接、互斥量等等)的简单技术。

    解读

    
    > 对象构造时获取资源,控制对资源的访问使之在对象的生命周期内始终保持有效,
    > 对象析构时释放资源。实际上把管理资源的责任托管给了一个对象
    > 一个类对象销毁时自动调用构造函数和析构函数 当函数栈帧销毁时对象自动销毁 
    > 实际上是因为这两个固有的"自动"成就了智能指针
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    优势

    不需要显式释放资源
    对象所需资源在其生命期内始终保持有效

    3.4智能指针的设计

    在这里插入图片描述

    在这里插入图片描述

    template<class T>
    class SmartPtr
    {
    public:
    	SmartPtr(T* ptr)
    		:_ptr(ptr)
    	{
    	
    	}
    
    	~SmartPtr()
    	{
    		cout << "delete: " << _ptr << endl;
    		delete _ptr;
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    
    int div()
    {
    	int a, b;
    	cin >> a >> b;
    	if (b == 0)
    		throw invalid_argument("除0错误");
    
    	return a / b;
    }
    
    void Func()
    {
    	SmartPtr<int> sp1(new int(1));
    	SmartPtr<int> sp2(new int(2));
    
    	cout << div() << endl;
    
    	*sp1 = 10;
    	cout << "* sp1 == " << *sp1 << endl;
    	cout << "* sp2 == " << *sp2 << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    3.5智能指针的拷贝问题

    1.代码问题

    template<class T>
    class SmartPtr
    {
    public:
    	SmartPtr(T* ptr)
    		:_ptr(ptr)
    	{
    	
    	}
    
    	~SmartPtr()
    	{
    		cout << "delete: " << _ptr << endl;
    		delete _ptr;
    	}
    
    	T& operator*()
    	{
    		return *_ptr;
    	}
    
    	T* operator->()
    	{
    		return _ptr;
    	}
    private:
    	T* _ptr;
    };
    
    int main()
    {
    	SmartPtr<int> sp1(new int(1));
    	SmartPtr<int> sp2(sp1);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述

    很明显这里时之前就讲过的深拷贝问题 函数结束时对同一块空间释放两次 有人说直接深拷贝不就ok了?

    而这样的回答实际上时忘记了我们设计智能指针的初衷 即它要像指针一样工作 对于指针的拷贝就是两个不同的指针指向同一块空间 那怎么办?

    有的人可能有这样的问题 之前的模拟实现容器时 我们自己搞了一个迭代器 为什么迭代器的浅拷贝没问题 而智能指针的而浅拷贝有问题?

    迭代器是一个类 对于一个类对象 当对象销毁 会调用析构函数 默认生成的析构函数对于内置类型不做处理 对于自定义类型调用他的析构函数 自己实现的迭代器没有手写析构函数 于是调用自动生成的 而在迭代器里 指针是一个内置类型 不做处理
    而在智能指针类里 我们手写了析构函数 并且在这个析构函数里还释放了指针指向的空间于是就造成了上述问题

    2.初代智能指针

    C++98#include auto_ptr;

    	template<class T>
    	class auto_ptr
    	{
    	public:
    		auto_ptr(T* ptr)
    			:_ptr(ptr)
    		{
    		
    		}
    
    		~auto_ptr()
    		{
    			if (_ptr)
    			{
    				cout << "delete:" << _ptr << endl;
    				delete _ptr;
    			}
    		}
    
    		//构造函数: 管理权转移
    		auto_ptr(auto_ptr<T>& ap)
    			:_ptr(ap._ptr)
    		{
    			ap._ptr = nullptr;
    		}
    
    		T& operator*()
    		{
    			return *_ptr;
    		}
    
    		T* operator->()
    		{
    			return _ptr;
    		}
    	private:
    		T* _ptr;
    	};
    
    	void test_auto()
    	{
    		auto_ptr<int> ap1(new int(1));
    		auto_ptr<int> ap2(ap1);
    
    		//ap1失去管理权 悬空指针
    		*ap1 = 1; 
    		*ap2 = 1;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    3.智能指针的发展

    在这里插入图片描述

    Boost是啥?
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    3.6智能指针的升级

    不需要拷贝的场景设置为禁止拷贝

    在这里插入图片描述

    C++98

    在这里插入图片描述

    C++11

    在这里插入图片描述

    C++11中类与对象推出的新功能 [补充讲解final/override关键字]

    在这里插入图片描述

    	template<class T>
    	class unique_ptr
    	{
    	public:
    		unique_ptr(T* ptr)
    			:_ptr(ptr)
    		{
    		
    		}
    
    		~unique_ptr()
    		{
    			if (_ptr)
    			{
    				cout << "delete:" << _ptr << endl;
    				delete _ptr;
    			}
    		}
    
    		T& operator*()
    		{
    			return *_ptr;
    		}
    
    		T* operator->()
    		{
    			return _ptr;
    		}
    
    		// C++11防拷贝
    		unique_ptr(const unique_ptr<T>& up) = delete;
    		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
    
    	/*
    	private:
    		// C++98防拷贝
    		// 1.声明为私有 --禁止在类外实现  
    		// 2.不实现     --编译器不自动生成浅拷贝
    		unique_ptr(const unique_ptr& up);
    		unique_ptr& operator=(const unique_ptr& up);
    	*/
    	private:
    		T* _ptr;
    	};
    
    	void test_unique()
    	{
    		unique_ptr<int> up1(new int(1));
    		//unique_ptr up2(up1);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    需要拷贝的场景 – shared_ptr[见下]

  • 相关阅读:
    精彩回顾 l Rust唠嗑室:Xline跨数据中心一致性管理
    【pen200-lab】10.11.1.35
    spark 列判空
    在Postgresql中进行关键字查找
    Collectors.toMap()方法——Java8
    一文熟悉 Go 的循环结构 —— for 循环
    linux(三) -- 系统管理
    H3C 6520X 配置IRF+BFD
    神经网络算法基本介绍,简单神经网络算法原理
    Spring Data JPA 学习笔记
  • 原文地址:https://blog.csdn.net/LHRan_ran_/article/details/133988985