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 ...
随机推荐
- antv-x6 使用及总结
1 简介 AntV是一个数据可视化(https://so.csdn.net/so/search?q=数据可视化&spm=1001.2101.3001.7020 )的工具(https://ant ...
- Object.defineProperty熬夜整理的用法,保证你看的明白!
Object.defineProperty的基本使用 <script> let personObj={ name:'何西亚', sex:'男' } //我们想给这个对象添加一个属性 // ...
- webpack配置scss
安装依赖: cnpm i sass-loader -D cnpm i node-sass -D node-sass尽量去使用cnpm去安装 创建index2.scss文件 div { h2 { bac ...
- elemntui-tab添加图标
<el-tabs :before-leave="moreState" v-model="activeName" @tab-click="hand ...
- golang uuid库介绍
简介: 在现代软件开发中,全球唯一标识符(UUID)在许多场景中发挥着重要的作用.UUID是一种128位的唯一标识符,它能够保证在全球范围内不重复.在Go语言中,我们可以使用第三方库github.co ...
- WPF内嵌Http协议的Server端
需求:有时后比如WPF,WinForm,Windows服务这些程序可能需要对外提供接口用于第三方服务主动通信,调用推送一些服务或者数据. 想到的部分实现方式: 一.使用Socket/WebSocket ...
- .NET MAUI 简介
欢迎使用.NET 多平台应用程序 UI.此版本标志着我们在统一 .NET 平台的多年旅程中的新里程碑.现在,您和超过 500 万其他 .NET 开发人员拥有面向 Android.iOS.macOS 和 ...
- 东吴名贤传<二>薛综传
古典记载 吴录曰:其先齐孟尝君封於薛.秦灭六国,而失其祀,子孙分散.汉祖定天下,过齐,求孟尝后,得其孙陵.国二人,欲复其封.陵.国兄弟相推,莫適受,乃去之竹邑,因家焉,故遂氏薛.自国至综,世典州郡, ...
- Xcode的Search Paths配置
在Xcode中的文件搜索路径配置有两个地方,一个是Project层的配置,一个是Target的配置. Project-Build Settings-Search Paths Target-Build ...
- electron nodejs idea 的 Terminal 默认是操作系统的 cmd.exe,他默认是GBK
问题1:idea 的 Terminal 默认是操作系统的 cmd.exe,他默认是GBK,尽量不要通过手段更改了,没意义.若要在控制台输出中文,最简单的方法是运行:chcp 65001 &&a ...