018-智能指针
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了018-智能指针相关的知识,希望对你有一定的参考价值。
参考技术A ◼ 传统指针存在的问题作用域
智能指针不要去指向栈空间的对象,否则易导致两次析构,智能指针设计是用来指向堆空间的对象的,如下两次析构,第一次是作用域内person指向栈空间的对象析构,第二次是智能指针指向的person对象的栈空间析构
此时如何通过智能指针p调用person对象中的方法呢?p->run();直接调用会报错。
通过运算符重载,重载运算符的功能,用指针运算符->时返回Person *的指针m_obj,即p->run()相当于m_obj->run();
智能指针是对传统指针的再次封装,构造时传入对象,里面有一个传统指针m_obj指向传入的对象内存地址,析构时,智能指针p释放掉,传统指针指向的对象内存也释放delete m_obj
调用方法时,通过运算符重载,返回指向对象内存地址的传统指针
智能指针是一个对象,重载了指针运算符后返回一个指针
传入数组申请了创建了10个对象,但仅仅只析构了一次
◼ shared_ptr的设计理念 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放
◼ 可以通过一个已存在的智能指针初始化一个新的智能指针
◼一个shared_ptr会对一个对象产生强引用(strong reference)
◼ 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着
可以通过shared_ptr的use_count函数获得强引用计数
◼ 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
◼ 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
◼ 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)
指向对象的智能指针销毁一个,use_count就会减一
IV.shared_ptr易出现的问题,两次析构,程序崩溃
V.shared_ptr循环引用的问题:相互持有,始终无法销毁
◼ weak_ptr会对一个对象产生弱引用
◼ weak_ptr可以指向对象解决shared_ptr的循环引用问题
若是使用普通指针,自己会写delete,不存在循环引用的问题,循环引用只是针对智能指针引起的
◼ unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
◼ 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
◼ 可以使用std::move函数转移unique_ptr的所有权
不能多个unique_ptr指向同一个对象,会报错
std::move转移智能指针的指向,p1指向person对象,后面转移给p0指向person对象
智能指针11
智能指针
智能指针存在的必要性
- malloc出来的空间,没有进行释放,存在内存泄漏的问题。
- 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存 泄漏。这种问题就叫异常安全
智能指针的使用及原理
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。
原理:
1. RAII特性
2. 重载operator*和opertaor->,具有像指针一样的行为
3. 解决浅拷贝的问题
C++98中的智能指针
auto_ptr:
edition1:
如何解决浅拷贝问题:资源转移。
缺点:转移资源,若此时想对原对象操作会出错。
namespace bit
template<class T>
class auto_ptr
public:
auto_ptr(T* ptr=nullptr)
:_ptr(ptr)
auto_ptr(auto_ptr<T>& pt):_ptr(pt._ptr)
pt._ptr = nullptr;
auto_ptr<T>& operator=(auto_ptr<T>& ap)
if (this != &ap)
// 此处需要将ap中的资源转移给this
// 但是不能直接转移,因为this可能已经管理资源了,否则就会造成资源泄漏
if (_ptr)
delete _ptr;
// ap就可以将其资源转移给this
_ptr = ap._ptr;
ap._ptr = nullptr; // 让ap与之前管理的资源断开联系,因为ap中的资源已经转移给this了
return *this;
~auto_ptr()
if (_ptr)
delete _ptr;
_ptr = nullptr;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
T* Get()
return _ptr;
private:
T* _ptr;
;
;
editio2:转移释放权限
namespace bit
template<class T>
class auto_ptr
public:
auto_ptr(T* ptr =nullptr)
:_ptr(ptr)
,owner(false)
if (_ptr)
owner = true;
auto_ptr(auto_ptr<T>& t)
:_ptr(t._ptr)
,owner(t.owner)
t.owner = false;
auto_ptr<T>& operator=(auto_ptr<T>& t)
if (this != &t)
if (this->_ptr && this->owner)
delete _ptr;
_ptr = t._ptr;
owner = t.owner;
t.owner = false;
return *this;
~auto_ptr()
if (_ptr && owner)
delete _ptr;
owner = false;
_ptr = nullptr;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
T* Get()
return _ptr;
private:
T* _ptr;
bool owner;
;
void TestAutoPtr()
bit::auto_ptr<int> ap1(new int);
*ap1 = 100;
bit::auto_ptr<int> ap2(ap1);
// 和我们对指针常规的认知有区别的
int* p1 = new int;
int* p2(p1);
*p1 = 10;
*p2 = 20;
delete p1;
p1 = p2 = nullptr;
// auto_ptr采用资源转移的方式虽然将浅拷贝的问题解决了,但是引用了新的问题
if (ap2.Get())
*ap2 = 2000;
if (ap1.Get())
*ap1 = 1000; // 代码会崩溃,因为ap1当中的资源已经转移走了,ap1当中的指针指向的空
bit::auto_ptr<int> ap3(new int);
*ap3 = 300;
bit::auto_ptr<int> ap4(new int);
*ap4 = 400;
ap3 = ap4;
//
// 致命的缺陷---可以会导致野指针
if (true)
bit::auto_ptr<int> ap5(ap2);
*ap5 = 100;
*ap2 = 200;
*ap1 = 300;
// 再来开if的作用域时,ap5已经将管理的资源释放掉了
// 而ap1和ap2根本就不知道,其内部的指针称为野指针了
// 如果通过ap1和ap2再访问资源时,代码就会出问题
*ap2 = 10;
int main()
TestAutoPtr();
return 0;
缺陷:可能会导致野指针的情况,比如上面的示例
所以:建议不要使用auto_ptr
C++11中的智能指针
C++11中unique_ptr指针
这个智能指针的作用就是防拷贝,既然拷贝可能会带来资源重复释放,资源泄露的问题,那我从根源上就不让你拷贝。
// unique_ptr: RAII + operator*()/operator->()+解决浅拷贝方式:禁止拷贝---资源独占
// 一份资源只能被一个对象来进行管理,对象之间不能共享资源
// 应用场景:只能应用与资源被一个对象管理 并且不会被共享的场景当中
// 缺陷:多个对象之间不能共享资源
// 负责释放new资源
template<class T>
class DFDef
public:
void operator()(T*& ptr)
if (ptr)
delete ptr;
ptr = nullptr;
;
// 负责:malloc的资源的释放
template<class T>
class Free
public:
void operator()(T*& ptr)
if (ptr)
free(ptr);
ptr = nullptr;
;
// 关闭文件指针
class FClose
public:
void operator()(FILE*& ptr)
if (ptr)
fclose(ptr);
ptr = nullptr;
;
namespace bite
// T: 资源中所放数据的类型
// DF: 资源的释放方式
// 定制删除器
template<class T, class DF = DFDef<T>>
class unique_ptr
public:
/
// RAII
unique_ptr(T* ptr = nullptr)
: _ptr(ptr)
~unique_ptr()
if (_ptr)
// 问题:_ptr管理的资源:可能是从堆上申请的内存空间、文件指针、malloc空间...
// delete _ptr; // 注意:此处的释放资源的方式不能写死了,应该按照资源类型不同找对应的方式释放
// malloc--->free
// new---->delete
// fopen--->fclose关闭
DF df;
df(_ptr);
// 具有指针类似的行为
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
T* Get()
return _ptr;
// 解决浅拷贝方式---资源独占,即防止被拷贝
// C++98
/*
将拷贝构造函数以及赋值运算符重载方法只声明不定义,并且需要将其权限设置为私有的
// 只声明不定义,没有将权限设置为private---不行的:因为方法别人可以再类外定义
*/
private:
unique_ptr(const unique_ptr<T, DF>&);
unique_ptr<T, DF>& operator=(const unique_ptr<T, DF>&);
// C++11: 可以让编译器不生成默认的拷贝构造以及赋值运算符重载---delete
// 在C++11当中,delete关键字的功能扩展:释放new申请的空间 用其修饰默认成员函数,表明:编译器不会生成了
// unique_ptr(const unique_ptr<T,DF>&) = delete; // 表明:编译器不会生成默认的拷贝构造函数
// unique_ptr<T,DF>& operator=(const unique_ptr<T,DF>&) = delete;// 表明:编译器不会生成默认的赋值运算符重载
private:
T* _ptr;
;
// 用户在外部可以对方法进行定义---在unique_ptr的类中如果将该权限设置为private的
//template<class T>
//unique_ptr<T>::unique_ptr(const unique_ptr<T>& up)
//
#include <memory>
void TestUniquePtr()
bite::unique_ptr<int> up1(new int);
bite::unique_ptr<int, Free<int>> up2((int*)malloc(sizeof(int)));
bite::unique_ptr<FILE, FClose> up3(fopen("12345.txt", "w"));
int main()
TestUniquePtr();
bite::unique_ptr<int> up1(new int);
// bite::unique_ptr<int> up2(up1);
bite::unique_ptr<int> up3(new int);
// up1 = up3;
///
// 标准库
unique_ptr<int> up4(new int);
// unique_ptr<int> up5(up4);
unique_ptr<int> up6(new int);
// up4 = up6;
return 0;
C’++11中的shared_ptr
// 负责释放new资源
template<class T>
class DFDef
public:
void operator()(T*& ptr)
if (ptr)
delete ptr;
ptr = nullptr;
;
// 负责:malloc的资源的释放
template<class T>
class Free
public:
void operator()(T*& ptr)
if (ptr)
free(ptr);
ptr = nullptr;
;
// 关闭文件指针
class FClose
public:
void operator()(FILE*& ptr)
if (ptr)
fclose(ptr);
ptr = nullptr;
;
#include <mutex>
namespace bite
// shared_ptr: 自身才是安全的---加锁:为了保证shared_ptr自身的安全性
template<class T, class DF = DFDef<T>>
class shared_ptr
public:
//
// RAII
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pCount(nullptr)
, _pMutex(new mutex)
if (_ptr)
// 此时只有当前刚刚创建好的1个对象在使用该份资源
_pCount = new int(1);
~shared_ptr()
Release();
/
// 具有指针类似的行为
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
T* Get()
return _ptr;
//
// 用户可能需要获取引用计数
int use_count()const
return *_pCount;
///
// 解决浅拷贝方式:采用引用计数
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
, _pMutex(sp._pMutex)
AddRef();
shared_ptr<T, DF>& operator=(const shared_ptr<T, DF>& sp)
if (this != &sp)
// 在和sp共享之前,this先要将之前的状态清空
Release();
// this就可以和sp共享资源以及计数了
_ptr = sp._ptr;
_pCount = sp._pCount;
_pMutex = sp._pMutex;
AddRef();
return *this;
private:
void AddRef()
if (nullptr == _ptr)
return;
_pMutex->lock();
++(*_pCount);
_pMutex->unlock();
void Release()
if (nullptr == _ptr)
return;
bool isDelete = false;
_pMutex->lock();
if (_ptr && 0 == --(*_pCount))
// delete _ptr;
DF df;
df(_ptr);
delete _pCount;
_pCount = nullptr;
isDelete = true;
_pMutex->unlock();
if (isDelete)
delete _pMutex;
private:
T* _ptr; // 用来接收资源的
int* _pCount; // 指向了引用计数的空间---记录的是使用资源的对象的个数
mutex* _pMutex; // 目的:保证对引用计数的操作是安全的
;
void TestSharedPtr()
bite::shared_ptr<int> sp1(new int);
bite::shared_ptr<int> sp2(sp1);
bite::shared_ptr<int> sp3(new int);
bite::shared_ptr<int> sp4(sp3);
sp3 = sp2; // sp2和sp3共享同一份资源了
sp4 = sp2; // sp2和sp4共享同一份资源了
// 这个语法也是C++11新的语法:在定义成员变量时,可以就地初始化
struct A
int a = 0;
int b = 0;
int c = 0;
;
// 线程函数
void ThreadFunc(bite::shared_ptr<A>& sp, int n)
for (int i = 0; i < n; ++i)
bite::shared_ptr<A> copy(sp);
copy->a++;
copy->b++;
copy->c++;
#include <thread>
void TestSharedPtrSafe()
bite::shared_ptr<A> sp(new A);
// t1对象将来就和对应的线程绑定一起了
thread t1(ThreadFunc, sp, 10000);
thread t2(ThreadFunc, sp, 10000);
// 线程等待
t1.join();
t2.join();
// 1. 代码会不会崩溃----->shared_ptr是否安全---没有崩溃:
// 2. A 对象中的各个成员的值是不是都是20000
cout << sp->a << endl;
cout << sp->b << endl;
cout << sp->c << endl;
/
// 利用标准库中的shared_ptr来进行测试
#include <memory>
void ThreadFuncstd(shared_ptr<A>& sp, int n)
for (int i = 0; i < nC++编程经验:智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!