C++的智能指针
#pragma once
/*
Smart pointer
智能指针;灵巧指针
智能指针三大件
//1.RAII
//2.像指针一样使用
//3.拷贝问题 ,指针指针需要的是浅拷贝,并且需要处理资源释放问题 ---> 引用计数
RAII
Resource Acquisition Is Initialization 资源获取即初始化
一种利用对象的生命周期来控制资源的技术 ,是一种思想
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
SmartPrt是RAII的一种产物
在linux下内存泄漏检测:linux下几款内存泄漏检测工具 https://blog.csdn.net/gatieme/article/details/51959654
在windows下使用第三方工具:VLD工具说明 https://blog.csdn.net/GZrhaunt/article/details/56839765
其他工具:内存泄漏工具比较 https://www.cnblogs.com/liangxiaofeng/p/4318499.html
C++11和boost中智能指针的关系
1. C++ 98 中产生了第一个智能指针auto_ptr.
2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
3. C++ TR1(Technical Report 1,技术引入1:草稿),引入了shared_ptr等。不过注意的是TR1并不是标准版。
4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost
的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
*/
//智能指针常规实现1
/*
//1.RAII
//2.像指针一样使用
//3.拷贝问题 ,指针指针需要的是浅拷贝,并且需要处理资源释放问题 ---> 引用计数
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr) :_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr; //同一个对象,delete一次后可以置空,之后delete 多次没关系.
// 但不同对象指向同一个资源,_ptr都是指向资源的地址,同一个非空地址delete两次就会出问题
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr; //返回地址,会优化->-> ... .当T为自定义类型时起效
}
private:
T* _ptr;
};
int div()
{
int a, b;
std::cin >> a >> b;
if (b == 0)
throw std::invalid_argument("除0错误");
return a / b;
}
void func()
{
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(new int(10));
*sp1 = 10;
std::cout<< *sp1<<std::endl;
std::cout<<div()<<std::endl;
}
int main()
{
try {
func();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
*/
//智能指针的发展历史
/*
C++98:auto_ptr(自动指针)
解决拷贝问题的方式:使用管理权转移方式(把资源转移给新指针,原来的指针置空)
存在的问题:资源转移后,原来的指针就成了垂悬指针.使用的人因不熟悉特性或疏忽遗漏,到导致野指针引用
//在C++11标准中已明确废弃,很多公司规范明确不能使用auto_ptr,基本上没什么价值了
#include<memory> //头文件
void func()
{
std::auto_ptr<int> sp1(new int(1)); //nullptr
std::auto_ptr<int> sp2(sp1); //sp1
*sp1 = 10; //奔溃
}
boost时期:
boost库是C++标准库的体验服,https://baike.baidu.com/item/Boost%E5%BA%93/10671694?fr=ge_ala
较为好用的智能指针
scoped_ptr
shard_ptr/weak_ptr
(
防拷贝:
C++98:
1.对拷贝构造和拷贝赋值 只声明,不实现.--- 什么都不做
2.设置为私有 --- 防止外部获取并在外部实现
C++11:
copy = delete
operator = delete
)
C++11:
unique_ptr (scoped_ptr发展)
原理:简单粗暴,防拷贝(防止拷贝) --- 解决了auto_ptr资源转移后产生的垂悬指针问题,但无法解决需要拷贝的场景
使用:不需要使用拷贝的场景就可以使用
shard_ptr/weak_ptr(boost:shard_ptr/weak_ptr发展)
原理:引用计数
线程安全: shared_ptr本身是线程安全的,但是shared_ptr管理的对象不一定是安全的
//shared_ptr简易模拟实现和使用测试
template<class T>
class shared_ptr
{
public:
//template <class U> explicit shared_ptr(U* p);
explicit shared_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)), _pmtx(new std::mutex()) //第一次定义
{}
//with deleter(4) .接收定制删除器版本
//template <class U, class D> shared_ptr(U* p, D del);
template <class T, class D>
shared_ptr(T* ptr, D del)
: _ptr(ptr), _pcount(new int(1)), _pmtx(new std::mutex()), _del(del)
{}
shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx) //拷贝
{
AddCount();
}
~shared_ptr()
{
Release();
}
private:
void Release()
{
_pmtx->lock();
bool deleteFlag = false;
if (--(*_pcount) == 0)//每次执行都会--, 如果--后为0,就析构
{
if (_ptr)
{
//delete _ptr;
_del(_ptr);//把_ptr传给 通过包装器接收的 来自外界或缺省的 定制删除器
}
delete _pcount;
//锁也要释放
//{ //解决方案一
//_pmtx->unlock();
//delelte _pmtx;
//return;
//}
//解决方案二 : 使用标志位
deleteFlag = true;
}
_pmtx->unlock(); //释放锁后,不能有解锁操作
if (deleteFlag)
{
delete _pmtx;
}
}
void AddCount()
{
_pmtx->lock();
++(*_pcount);
_pmtx->unlock();
}
public:
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//先析构掉自己的资源
if (sp._ptr != _ptr)
{
Release();
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx; //拷贝时也要把锁烤过来
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr; //返回地址
}
T* get() const //getPtr
{
return _ptr;
}
int use_count() //返回引用的对象个数
{
return *_pcount;
}
private:
T* _ptr; //管理资源的指针
int* _pcount; //动态开辟的计数器,用于引用计数
std::mutex* _pmtx; //多线程时,保障临界资源的安全
std::function<void(T*)> _del = [](T*ptr) { //D是可调用对象类型,具体类型有多种,functional类型能够一个定义全部接收
std::cout << "default function<void(T*)>" << std::endl;
delete ptr; //缺省值
};
};
struct Date
{
int _year = 0;
int _month = 0;
int _day = 0;
};
void SharePtrFunc(shared_ptr<Date>& sp, size_t n, std::mutex& mtx) {
std::cout << sp.get() << std::endl;
for (size_t i = 0; i < n; ++i)
{
//验证1:验证智能指针本身是否安全,
shared_ptr<Date> copy(sp);
{
std::unique_lock<std::mutex> lk(mtx);//守护锁
copy->_year++;
copy->_month++;
copy->_day++;
//验证二: 智能指针 管理的资源不一定是线程安全的,两个线程++了2n 次是最终结果,正常情况下加了2n次,通过加锁和不加锁来验证
}
}
}
int main()
{
shared_ptr<Date> p(new Date);
std::cout << p.get() << std::endl;
const size_t n = 10000;
std::mutex mtx;
std::thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));
std::thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));
t1.join();
t2.join();
std::cout << p.use_count() << std::endl; //如果结果是1,说明线程安全. 否则说明不安全
std::cout<<p->_year<<std::endl;
std::cout<<p->_month<<std::endl;
std::cout<<p->_day<<std::endl;
return 0;
}
*/
//shared_prt 循环引用 问题
/*
使用C++提供的weak_ptr(弱指针),用于解决shared_ptr循环引用的问题.
weak_prt是专门用于解决shared_ptr的循环引用问题的类,特殊的智能指针,不符合RAII
weak_ptr的三个构造函数:
//1.无参构造
constexpr weak_ptr() noexcept;
//2.拷贝构造
1.weak_ptr (const weak_ptr& x) noexcept;
2.template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
//3.根据shared_ptr构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
//从三个拷贝构造来看,weak_ptr没有支持使用资源来构造,说明weak_ptr不是RAII指针,不是常规的智能指针
//特点
//1.他不是常规的智能指针,不支持RAII
//2.一样像指针一样(是特殊的智能指针)
//3.专门设计出来,辅助解决shared_ptr的循环引用问题
解决循环引用问题的核心原理是不增加引用计数
//weak_ptr 简易实现,仅仅能符合简单场景,用于理解weak_ptr ---- 非常简易,库中实现很困难
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr) //默认生成的不管理资源
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get()) //帮助shared_ptr管理资源
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr; //返回地址
}
T* get() const //getPtr
{
return _ptr;
}
private:
T* _ptr;
};
struct ListNode
{
/.* 1.有智能指针类型不匹配问题
//ListNode* prev;
//ListNode* next;
n1->next = n2; //没有异常的正常情况下,是没有问题的
n2->prev = n1; //使用shared_ptr管理ListNode后,类型不匹配,无法转换
//需要重新封装next和prev,使用shared_ptr封装next和prev
*./
/.* 2.有循环引用问题
//shared_ptr<ListNode> _prev; //智能指针封装的list
//shared_ptr<ListNode> _next;
*./
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
int _val;
~ListNode()
{
std::cout << "~ListNode()" << std::endl; //调试用 -- 看是否执行析构
}
};
void test_shared_cycle() //测试共享指针的循环问题
{
//{//一:常规写法,没有异常的正常情况下没有问题,但是异常出来以后,就不推荐这种写法
// ListNode* n1 = new ListNode;
// ListNode* n2 = new ListNode; //因为,从这里开始,如果抛异常,则n1的资源可能没有释放,--> 导致内存泄露
// n1->next = n2;
// n2->prev = n1;
// delete n1;
// delete n2;
//}
{//二:
shared_ptr<ListNode> n1(new ListNode);
//shared_ptr<ListNode> n2 = new ListNode; //隐式类型转换方式构造,调用了默认构造,使用explicit后不支持
shared_ptr<ListNode> n2{ new ListNode }; // 花括号初始化,等价于直接调用默认构造 ,注意,并不是=使用explicit后用=会报错
//shared_ptr<ListNode> n3{ new ListNode }; //花括号初始化,编译器提供的支持,
//现象1,只有n1->_obj = n2或 n2->obj = n1;时,可以正常析构
//现象2,当n1,n2循环引用时,无法正常析构了 --- shared_ptr的缺陷
n1->_next = n2;
n2->_prev = n1;
//对现象2的分析;
/.*
核心原因,两个对象相互引用:属于同一个对象的的内部资源,引用了第二个对象.而另外一个对象的内部资源,也反向引用第一个对象,
两对象互相引用,在析构时两边都在等待对方先析构,形成闭环,导致类似死锁的现象
两个对象相互引用后,引用计数都为2,析构时两个对象都为1,无法减到0.导致无法真正析构资源. --- 属于shared_ptr的缺陷
*./
//C++11对现象2的解决方案:使用weak_ptr
}
}
int main()
{
test_shared_cycle();
return 0;
}
*/
//定制删除器
/*
不同类型的资源有不同的析构方式, shared_ptr通过可调用对象:定制删除器 来实现不同方式的资源释放
定制删除器:就是可调用对象,实现一个定制的,具有删除功能的可调用对象,传给shared_ptr
template<class T>
struct DeleteArray //函数对象式定制删除器
{
void operator()(T* ptr) //shared_ptr会把资源的指针传进来
{
std::cout << " void operator()(T* ptr)" << std::endl;
delete[] ptr;
}
};
struct Date
{
int _year = 0;
int _month = 0;
int _day = 0;
};
int main()
{
shared_ptr<Date> sp0{ new Date };
shared_ptr<Date> spa1{ new Date[4],DeleteArray<Date>() }; // 函数对象式定制删除器 释放资源
shared_ptr<Date> spa2{ new Date[10],[](Date* ptr) {delete[] ptr; } }; //lambda表达式式定制删除器 释放资源
shared_ptr<FILE> spF3{ fopen("test.cpp","r"),[](FILE* ptr) {fclose(ptr); } }; //不只是堆空间,还可以是文件
return 0;
}
*/
C++的智能指针的更多相关文章
- enote笔记法使用范例(2)——指针(1)智能指针
要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...
- C++11 shared_ptr 智能指针 的使用,避免内存泄露
多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...
- C++智能指针
引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...
- EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针
一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...
- 智能指针shared_ptr的用法
为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...
- 智能指针unique_ptr的用法
unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...
- 基于C/S架构的3D对战网络游戏C++框架_05搭建系统开发环境与Boost智能指针、内存池初步了解
本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...
- C++ 引用计数技术及智能指针的简单实现
一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...
- C++11智能指针读书笔记;
智能指针是一个类对象,而非一个指针对象. 原始指针:通过new建立的*指针 智能指针:通过智能指针关键字(unique_ptr, shared_ptr ,weak_ptr)建立的指针 它的一种通用实现 ...
- 「C++」理解智能指针
维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...
随机推荐
- expres实现登录与修改密码
登录模块 如果登录的时候,昵称在数据库中查询的是不唯一值.需要提示用户登录失败. 即使用户密码输入正确. 然后将该消息通知相关人员,立即进行排查问题. var express = require('e ...
- elementUI封装 el-dialog
讲解 // 讲解: @close="$emit('update:show1', false)"是子组件跟新父组件中的某值show1,将值变为false // :visible.sy ...
- 如何区分Unity国内版和国际版
从这三个地方都可以判断使用的Unity是国内版本还是国际版,国内版的版本号后面会多出c1,而国际版则不会有c1结尾. 安装目录名 unity hub - 安装 - 查看当前安装的Unity各版本 un ...
- 5.11 汇编语言:仿写IF条件语句
条件语句,也称为IF-ELSE语句,是计算机编程中的一种基本控制结构.它允许程序根据条件的真假来执行不同的代码块.条件语句在处理决策和分支逻辑时非常有用.一般来说,条件语句由IF关键字.一个条件表达式 ...
- Python 封装zabbix-get接口
Zabbix 是一款强大的开源网管监控工具,该工具的客户端与服务端是分开的,我们可以直接使用自带的zabbix_get命令来实现拉取客户端上的各种数据,在本地组装参数并使用Popen开子线程执行该命令 ...
- 技嘉水雕II 360水冷散热器评测:稳压340W i9-14900K
一.前言:极简卡扣连锁风扇设计 再多风扇也只需2根线 如今这个年代,DIY主机几乎都会配大量的RGB风扇,然而"光污染"虽然带来了视觉感官享受,在理线方面却非常繁琐. 就拿360水 ...
- Java并发(十)----线程之守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束.有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束. 例: log.debug(&q ...
- Windows平台安装Oracle11.2.0.4客户端报错INS-30131
之前为解决EXP-00003错误给出了安装Oracle11.2.0.4的解决方案,自己测试是没问题的,客户自己安装反馈遇到报错INS-30131,MOS有一篇文章: E1: DB: Error INS ...
- Linux-nmon系统监控工具
一.Nmon介绍 Nmon得名于 Nigel 的监控器,是IBM的员工 Nigel Griffiths 为 AIX 和 Linux 系统开发的,使用 Nmon 可以很轻松的监控系统的 CPU.内存.网 ...
- Centos8 安装 Redis6.0.16
下载,解压,编译,安装 安装至 /opt/redis/redis-6.0.16 目录 tar xvf redis-6.0.16.tar.gz gcc --version cd redis-6.0.16 ...