【C++ 学习 ㊱】- 智能指针详解

目录

一、为什么需要智能指针?

二、智能指针的原理及使用

三、auto_ptr

3.1 - 基本使用

3.2 - 模拟实现

四、unique_ptr

4.1 - 基本使用

4.2 - 模拟实现

五、shared_ptr

5.1 - 基本使用

5.2 - 模拟实现

六、weak_ptr

6.1 - shared_ptr 的循环引用问题

6.2 - 模拟实现

七、定制删除器



一、为什么需要智能指针?

问题引入:

#include <iostream>
using namespace std;
​
int division(int x, int y)
{if (y == 0)throw "Division by zero condition!";elsereturn x / y;
}
​
void func()
{string* p1 = new string("hello");pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" };
​int a = 0, b = 0;cin >> a >> b;cout << division(a, b) << endl;
​delete p1;delete p2;
}
​
int main()
{try {func();}catch (const char* errmsg) {cout << errmsg << endl;}catch (...) {cout << "Unknow exception" << endl;}return 0;
}

调用 func 函数:

  1. 如果在执行 string* p1 = new string("hello"); 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常;

  2. 如果在执行 pair<string, string>* p2 = new pair<string, string>{ "hello", "你好" }; 语句的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 指向的动态分配的内存没有被释放,最终会造成内存泄漏

  3. 如果在调用 division(a, b) 函数的过程中抛出了异常,那么在 main 函数中会捕获到抛出的异常,但是 p1 以及 p2 指向的动态分配的内存都没有被释放,最终也会造成内存泄漏


二、智能指针的原理及使用

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

在对象构造时获取资源,对资源的访问在对象生命周期内始终有效;在对象析构时,即对象生命周期结束时,释放资源

SmartPtr.h

#pragma once
​
namespace yzz
{template<class T>class SmartPtr{public:SmartPtr(T* ptr = nullptr) : _ptr(ptr) { }
​~SmartPtr(){if (_ptr){delete _ptr;_ptr = nullptr;}}T& operator*(){return *_ptr;}
​T* operator->(){return _ptr;}private:T* _ptr;};
}

test.cpp

#include <iostream>
#include "SmartPtr.h"
using namespace std;
​
int division(int x, int y)
{if (y == 0)throw "Division by zero condition!";elsereturn x / y;
}
​
void func()
{yzz::SmartPtr<string> sp1 = new string("hello");cout << *sp1 << endl;
​yzz::SmartPtr<pair<string, string>> sp2 = new pair<string, string>{ "hello", "你好"};cout << (*sp2).first << " : " << (*sp2).second << endl;cout << sp2->first << " : " << sp2->second << endl;  // 为了可读性,编译器将 sp2->->first/second 优化成了 sp2->first/second
​int a = 0, b = 0;cin >> a >> b;cout << division(a, b) << endl;// 在 func 函数中,无论有没有异常抛出,// func 函数结束后,都会自动调用 sp1 以及 sp2 对象的析构函数释放动态分配的内存,// 不再需要手动 delete
}
​
int main()
{try {func();}catch (const char* errmsg) {cout << errmsg << endl;}catch (...) {cout << "Unknow exception" << endl;}return 0;
}


三、auto_ptr

C++98/03 标准中提供了 auto_ptr 智能指针

3.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
​~A(){cout << "~A()" << endl;}
​int _i;
};
​
int main()
{auto_ptr<A> ap1(new A(1));auto_ptr<A> ap2(new A(2));
​auto_ptr<A> ap3(ap1);  // 管理权转移auto_ptr<A> ap4(new A(4));ap4 = ap2;  // 管理权转移
​// cout << ap1->_i << endl;  // error// cout << ap2->_i << endl;  // errorcout << ap3->_i << endl;  // 1cout << ap4->_i << endl;  // 2return 0;
}

3.2 - 模拟实现

namespace yzz
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr) : _ptr(ptr){ }
​~auto_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}
​auto_ptr(auto_ptr<T>& ap): _ptr(ap._ptr){ap._ptr = nullptr;}
​auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){if (_ptr)delete _ptr;
​_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}
​T& operator*(){return *_ptr;}
​T* operator->(){return _ptr;}private:T* _ptr;};
}


四、unique_ptr

C++11 标准废弃了 auto_ptr,新增了 unique_ptr、shared_ptr 以及 weak_ptr 这三个智能指针

4.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
​~A(){cout << "~A()" << endl;}
​int _i;
};
​
int main()
{unique_ptr<A> up1(new A(1));unique_ptr<A> up2(new A(2));
​// unique_ptr<A> up3(up1);  // errorunique_ptr<A> up4(new A(4));// up4 = up2;  // errorreturn 0;
}

4.2 - 模拟实现

从上面的例子中可以看出,unique_ptr 的实现原理就是简单粗暴的防拷贝

namespace yzz
{template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr) : _ptr(ptr){ }
​~unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}
​unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
​T& operator*(){return *_ptr;}
​T* operator->(){return _ptr;}private:T* _ptr;};
}


五、shared_ptr

5.1 - 基本使用

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
​~A(){cout << "~A()" << endl;}
​int _i;
};
​
int main()
{shared_ptr<A> sp1(new A(1));shared_ptr<A> sp2(new A(2));cout << sp1.use_count() << endl;  // 1cout << sp2.use_count() << endl;  // 1cout << "-------------" << endl;
​shared_ptr<A> sp3(sp1);cout << sp1.use_count() << endl;  // 2cout << sp3.use_count() << endl; // 2sp3->_i *= 10;cout << sp1->_i << endl;  // 10cout << sp3->_i << endl;  // 10cout << "-------------" << endl;
​shared_ptr<A> sp4(new A(4));sp4 = sp2;cout << sp2.use_count() << endl;  // 2cout << sp4.use_count() << endl; // 2sp4->_i *= 10;cout << sp2->_i << endl;  // 20cout << sp4->_i << endl;  // 20return 0;
}

5.2 - 模拟实现

shared_ptr 的实现原理:通过引用计数的方式实现多个 shared_ptr<T> 类对象之间共享资源

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源,如果计数为 0,说明该对象是资源的最后一个使用者,于是将资源释放,否则就不能释放,因为还有其他对象在使用该资源

namespace yzz
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCnt(new int(1)){ }
​~shared_ptr(){if (--(*_pCnt) == 0){if (_ptr){delete _ptr;_ptr = nullptr;}delete _pCnt;}}
​shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pCnt(sp._pCnt){++(*_pCnt);}
​shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){if (--(*_pCnt) == 0){if (_ptr)delete _ptr;
​delete _pCnt;}
​_ptr = sp._ptr;_pCnt = sp._pCnt;++(*_pCnt);}return *this;}
​T& operator*(){return *_ptr;}
​T* operator->(){return _ptr;}
​int use_count() const{return *_pCnt;}
​T* get() const{return _ptr;}private:T* _ptr;int* _pCnt;};
}

 


六、weak_ptr

6.1 - shared_ptr 的循环引用问题

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
​~A(){cout << "~A()" << endl;}
​int _i;
};
​
struct ListNode
{A _val;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;
};
​
int main()
{shared_ptr<ListNode> sp1(new ListNode);shared_ptr<ListNode> sp2(new ListNode);// A(int x = 0)// A(int x = 0)cout << sp1.use_count() << endl;  // 1cout << sp2.use_count() << endl;  // 1
​sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl;  // 2cout << sp2.use_count() << endl;  // 2// 出现内存泄漏问题return 0;
}

解决方案

#include <memory>
#include <iostream>
using namespace std;
​
class A
{
public:A(int x = 0) : _i(x){cout << "A(int x = 0)" << endl;}
​~A(){cout << "~A()" << endl;}
​int _i;
};
​
struct ListNode
{A _val;// weak_ptr 不增加引用计数,并且在可以访问资源的同时,不参与释放资源weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;
};int main()
{shared_ptr<ListNode> sp1(new ListNode);shared_ptr<ListNode> sp2(new ListNode);// A(int x = 0)// A(int x = 0)cout << sp1.use_count() << endl;  // 1cout << sp2.use_count() << endl;  // 1sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.use_count() << endl;  // 1cout << sp2.use_count() << endl;  // 1// ~A()// ~A()return 0;
}

6.2 - 模拟实现

namespace yzz
{template<class T>class weak_ptr{public:weak_ptr() : _ptr(nullptr){ }
​weak_ptr(const shared_ptr<T>& sp): _ptr(sp.get()){ }
​weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}
​T& operator*(){return *_ptr;}
​T* operator->(){return _ptr;}private:T* _ptr;};
}


七、定制删除器

template<class T>
struct DeleteArrayFunc
{void operator()(T* ptr){delete[] ptr;}
};
​
template<class T>
struct FreeFunc
{void operator()(T* ptr){free(ptr);}
};
​
int main()
{shared_ptr<A> sp1(new A(1));  // ok
​// shared_ptr<A> sp2(new A[5]);   // errorshared_ptr<A> sp2(new A[5], DeleteArrayFunc<A>());   // ok
​// shared_ptr<A> sp3((A*)malloc(sizeof(A)));  // errorshared_ptr<A> sp3((A*)malloc(sizeof(A)), FreeFunc<A>());  // ok
​// shared_ptr<FILE> sp4(fopen("test.cpp", "r"));  // errorshared_ptr<FILE> sp4(fopen("test.cpp", "r"), [](FILE* fp) { fclose(fp); });  // errorreturn 0;
}

shared_ptr 的模拟实现

#include <functional>
​
namespace yzz
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr): _ptr(ptr), _pCnt(new int(1)), _del([](T* ptr) { delete ptr; }){ }
​template<class D>shared_ptr(T* ptr, D del): _ptr(ptr), _pCnt(new int(1)), _del(del){ }
​~shared_ptr(){if (--(*_pCnt) == 0){if (_ptr){_del(_ptr);_ptr = nullptr;}delete _pCnt;}}// ... ...private:T* _ptr;int* _pCnt;std::function<void(T*)> _del;};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/174067.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是Node.js的调试器(debugger)工具?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

JavaWeb Day10 案例 准备工作

目录 一、需求说明 二、环境搭建 &#xff08;一&#xff09;数据库 &#xff08;二&#xff09;后端 ①controller层 1.DeptController.java 2.EmpController.java ②mapper层 1.DeptMapper.java 2.EmpMapper.java ③pojo层 1.Dept.java 2.Emp.java 3.Result.ja…

基于ssm酒店管理系统

基于ssm酒店管理系统 摘要 基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的酒店管理系统是一种利用现代化技术手段来提高酒店管理效率和服务质量的信息化管理系统。该系统整合了Spring框架的依赖注入、Spring MVC框架的请求处理和MyBatis框架的持久化操作&a…

【Android Studio调试报错】setContentView(R.layout.activity_main);

报错如下&#xff1a; 解决方法&#xff1a; 1、把参数删除到只剩 .&#xff0c;用自动补齐的方式来查看当前文件的位置是不是&#xff0c;当前左侧工程中layout 所在的位置。在的话它会在自动补齐列表有选项。否则我们选中第一个。 2、选中之后是这样的 然后问题解决&#xf…

智慧工地管理云平台源码,Spring Cloud +Vue+UniApp

智慧工地源码 智慧工地云平台源码 智慧建筑源码支持私有化部署&#xff0c;提供SaaS硬件设备运维全套服务。 互联网建筑工地&#xff0c;是将互联网的理念和技术引入建筑工地&#xff0c;从施工现场源头抓起&#xff0c;最大程度的收集人员、安全、环境、材料等关键业务数据&am…

进行 “最佳价格查询器” 的开发(多种并行方式的性能比较)

前置条件 public class Shop {private final String name;private final Random random;public Shop(String name) {this.name name;random new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));}public double getPrice(String product) {return calculatePrice…

3DMAX建模基础教程:常用工具补充

在本篇3DMAX建模基础教程中&#xff0c;我们将为您介绍一些常用的工具及其功能。熟练掌握这些工具将大大提高您的建模效率。 1️⃣ 选择与变换工具 选择工具&#xff1a;帮助您选择对象&#xff0c;可以通过单击对象或按组选择。 变换工具&#xff1a;对选定的对象进行移动、…

数据结构 | 带头双向循环链表专题

数据结构 | 带头双向循环链表专题 前言 前面我们学了单链表&#xff0c;我们这次来看一个专题带头的双向循环链表~~ 文章目录 数据结构 | 带头双向循环链表专题前言带头双向循环链表的结构实现双向链表头文件的定义哨兵位初始化创建节点尾插尾删头插头删打印查找指定位置前插入…

DVWA - 3

文章目录 XSS&#xff08;Dom&#xff09;lowmediumhighimpossible XSS&#xff08;Dom&#xff09; XSS 主要基于JavaScript语言进行恶意攻击&#xff0c;常用于窃取 cookie&#xff0c;越权操作&#xff0c;传播病毒等。DOM全称为Document Object Model&#xff0c;即文档对…

Linux-AWK(应用最广泛的文本处理程序)

目录 一、awk基础 二、awk工作原理 三、OFS输出分隔符 四、awk的格式化输出 五、awk模式pattern 一、awk基础 使用案例&#xff1a; 1.准备工作 请在Linux中执行以下指令 cat -n /etc/passwd > ./passwd 练习&#xff1a; 1.从文件 passwd 中提取并打印出第五行的内…

微软允许OEM对Win10不提供关闭Secure Boot

用户可能将无法在Windows 10电脑上安装其它操作系统了&#xff0c;微软不再要求OEM在UEFI 中提供的“关闭 Secure Boot”的选项。 微软最早是在Designed for Windows 8认证时要求OEM的产品必须支持UEFI Secure Boot。Secure Boot 被设计用来防止恶意程序悄悄潜入到引导进程。问…

Linux编辑器:vim的简单介绍及使用

目录 1.什么是vim 2.vim的基本概念 3.vim 的基本操作 4. 各模式下的命令集 4.1 正常模式命令集 4.2 末行模式命令集 5.补充 5.1 vim支持多文件编辑 5.2 vim 的配置 1.vim 配置原理 2. 常用简单配置选项&#xff1a; 3. 使用插件 1.什么是vim Vim 是从 vi 发展出…