c++ || 智能指针

i am stu
//cin scanf输入数据会将空格后面刷掉
cin.getline() //获取到一行

在这里插入图片描述

RAII(Resource Acquisition is Inintialization)

获取资源即初始化,使用局部对象来管理资源的技术称为资源获取即初始化

RAII的理念是将资源的获取放在类的构造函数里,资源的释放放在类的析构函数里,在类的生存期结束的时候,析构函数会被自动调用,对应的资源将会释放

资源:操作系统中有限的东西如内存,网络套接字,互斥量,文件句柄
局部对象:存储在栈的对象,生命周期有操作系统管理

  • 资源的使用
    1获取资源(构造对象)
    2使用资源
    3销毁对象(析构对象)
  • RAII过程四个步骤
    1设计一个类封装资源
    2构造函数中初始化
    3析构函数中执行销毁操作
    4使用时定义一个该类的对象

如何让资源自动销毁:使用RAII,利用c++语言局部对象自动销毁的特性来控制资源的生命周期

堆上空间进行自动化管理 – 利用对象自动析构的机制

int* p = new int(10); //裸指针
p生成临时auto_ptr对象 — 隐式构造
使用临时对象拷贝构造a_p
析构临时对象

  • 裸指针存在的问题

1.难以区分指向的是单个对象还是一个数组;
2使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;
3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;
4.即便已经确定了销毁指针的方法,由于1的原因,仍然
无法确定到底是用delete(销毁单个对象)还是delete[(销毁一个数组);

5.假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),**有且仅有一次销毁指针操作;**任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为;
6.理论上没有方法来分辨一个指针是否处于悬挂状态;

智能指针

  • 程序用堆来存储动态分配的对象即在程序运行时分配的对象,当对象不在使用时,需要显示的销毁对象。

  • 在c++的动态内存管理中,经常会出现两个问题,1是忘记释放内存而导致内存泄漏,2是尚有指针引用内存的情况下就释放了它,会产生非法内存的指针。

  • C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。

  • 智能指针的基本哲学是RAII,将内存申请放在对象构造的时候,而在对象析构的时候自动释放,利用类的构造函数和析构函数来管理资源

智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现

  • 智能指针的设计
  1. 通过普通指针创建
int* p = new int(10); //堆区开辟一个空间
unique_ptr<int> up(p);
  1. 通过new方法创建
unique_ptr<int> up1(new int(10));
  1. 通过make_unique创建
unique_ptr<int> up2 = make_unique<int>(10);
  • 强智能指针: 资源每被强智能指针引用一次,引用计数+1,释放引用计数-1,(shared_ptr)

强智能指针交叉引用会出现资源泄露的问题,引入弱智能指针解决

  • 弱智能指针: 仅仅起到观察作用,观察对象是否还存在(对象的引用计数是否等于0),不改变资源的引用计数,(weak_ptr)

弱智能指针只是简单的引用,不能控制对象的生死,不能使用‘->’或‘ * ’ , 必须要把弱智针提升成强指针之后才能去访问类的成员

  • 定义或生成资源的地方一定是强智能指针,引用资源的地方一定使用弱智能指针

auto_ptr (c11弃用)

  1. 动态分配对象new,malloc以及当对象不再需要是自动执行清理
  2. 拷贝构造和等号运算符重载将源智能指针置空
  3. 不允许隐式构造

构造函数与析构函数

  • auto_ptr 析构时会删除他所拥有的对象,即两个auto_ptr不能同时拥有同一个对象,不能使用同一个裸指针赋值/初始化多个auto_ptr
int *p = new int(0);
auto_ptr<int> ap(p);
auto_ptr<int> bp(p);//X 不能指向同一个对象
  • auto_ptr析构函数使用的是delete ,而不是delete[] ,不应该用auto_ptr来管理数组指针
  1. ~Mauto_ptr()
    {
    delete _ptr; //对于数组也是直接delete
    }
    // 在析构时 他针对所有的参数类型都是直接进行delete,像数组需要delete[]_ptr;他也是直接delete掉 是他被淘汰的原因之一
  2. 它的release()和reset()都将当前指针置为空,无意义
template<class _Ty>
class MyDeletor  //完全泛化的删除器
{
public:
	//MyDeletor() = default;
	MyDeletor() {}
	void operator()(_Ty* ptr) const
	{
		if (ptr != nullptr)
		{
			delete ptr;
		}
	}
};
template<class _Ty> 部分特化的删除器
class MyDeletor<_Ty[]>
{
public:
	MyDeletor() = default;
	void operator()(_Ty* ptr) const
	{
		if (ptr != nullptr)
		{
			delete[]ptr;
		}
	}
};

拷贝构造与赋值函数

  • auto_ptr的拷贝构造函数和赋值函数的参数为引用而不是常引用
  • 拷贝或赋值的目标对象将先释放自己原来所拥有的对象
  1. auto_ptr被拷贝或被赋值后,已失去对原对象的所有权,这时对这个auto_ptr的提领(dereference)操作是不安全的

注意:一定要避免auto_ptr作为函数参数按值传递, 在函数调用时 函数的作用域会产生一个局部对象来接收传入的auto_ptr(拷贝构造),传入的实参就是去对原有对象的所有权,而该对象会在函数退出时被局部auto_ptr删除

int *p = new int(0);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2 = ap1;
cout<<*ap1;//X ap1已经失去对q指针的所有权
  1. 因为auto_ptr不具有值语义,所以auto_ptr不能被用在STL标准容器中。

值语义(value semantics):所有内置类型都具有值语义
什么样的类没有值语义?(none-value-semantics type,NVST)
1.有virtual function的类
2.包含NVST成员的类
3.NVST的衍生类
4.定义自己的=operator的类
5.继承virtual基类的衍生类

  1. 提领操作(dereference)
    a.返回其所拥有的对象的引用
    b.实现了通过auto_ptr调用其所拥有的对象的成员

注意事项

  1. auto_ptr不能指向数组
  2. auto_ptr不能共享所有权
  3. auto_ptr不能通过复制操作来初始化
  4. auto_ptr不能放入容器中使用
  5. auto_ptr不能作为容器的成员

unique_ptr(唯一性智能指针)

c11中使用unique_ptr替代auto_ptr

  1. unique_ptr对象不能进行复制操作,只能进行移动操作
  2. unique_ptr可以‘独占’的拥有他所指向的对象
  3. unique_ptr在某一时刻只能有一个指针指向该对象,两个unique_ptr不能指向同一个对象
  4. unique_ptr对象中保存指向某个对象的指针,当他本身本身被删除或离开作用域时会自动释放其所指向对象所占用的资源
创建

将一个new操作符返回的指针传递给unique_ptr的构造函数

unique_ptr<int> pInt(new int(5));
cout<<*pInt;

无法进行复制构造和赋值操作

可以进行移动构造和移动赋值操作

如果想要将一个unique_ptr的内存交给另一个unique_ptr对象来管理,可以用过std::move()函数转移指针的所有权,转移之后,当前对象不再持有该内存,新的对象将获得专属所有权。

unique_ptr<int> pInt(new int(5));
unique_ptr<int> pInt2 = std::move(pInt);//转移所有权
cout<<*pInt;//错误 
cout<<*pInt2;//正确
unique_ptr<int> pInt3(std::move(pInt2));

使用场景

  1. 为动态申请资源提供安全保证
    只要unique_ptr指针创建成功,其析构函数都会被调用,确保动态资源被释放
  2. 返回函数内动态申请资源的所有权
unique_ptr<int> func( int x)
{
	unique_ptr<int> pInt(new int(x)) ;
	return pInt; //返回unique_ptr5.
}
int main( )
{
    int a = 5;
    unique_ptr<int> ret = func(a);
    cout <<*ret << endl;
}
//函数结束后,自动释放资源

  1. 容器内保存指针
int main( ) {
vector<unique_ptr<int> > vec;
unique_ptr<int> pInt(new int(5));
vec.push_back(std : :move(pInt)); //使用移动语义
}

  1. 管理动态数组
int main() 
{
	unique_ptr<int[ ]> p(new int[5] {12345});
	p[0] = 0; //重载了operator[]
}

// class unique_ptr 
template<class _Ty,class _Dx = MyDeletor<_Ty> >
class my_unique_ptr
{
public:
	// old 
	//typedef _Ty* pointer;
	//typedef _Ty  element_type;
	//typedef _Dx  delete_type;  删除器
	// c11 new 
	using pointer = _Ty*;      //typedef _Ty* pointer;
	using element_type = _Ty;  //typedef _Ty  element_type;
	using delete_type = _Dx;   //typedef _Dx  delete_type;
private:
	_Ty* _Ptr;
	_Dx  _myDeletor;//空的 占位符 占1字节 对其方式就站4字节
public:
	my_unique_ptr(const my_unique_ptr&) = delete;
	my_unique_ptr& operator=(const my_unique_ptr&) = delete;

	my_unique_ptr(pointer _P = nullptr) :_Ptr(_P) { cout << "my_unique_ptr: " << this << endl; }
	~my_unique_ptr()
	{
		if (_Ptr != nullptr)
		{
			_myDeletor(_Ptr);
			_Ptr = nullptr;
		}
		cout << "~my_unique_ptr: " << this << endl;
	}
	my_unique_ptr(my_unique_ptr&& _Y)
	{ //移动构造
		_Ptr = _Y._Ptr;
		_Y._Ptr = nullptr;
		cout << " move copy my_unique_ptr : " << this << endl;
	}
	template<class _Uy>
	my_unique_ptr& operator=(_Uy* _p )
	{ //移动赋值
		if (this->_Ptr ==(_Ty*) _p) return *this;
		if (_Ptr != nullptr) { _myDeletor(_Ptr); }
		_Ptr = _p;
		return *this;
	}
	my_unique_ptr& operator=(my_unique_ptr&& _Y)
	{
		if (this == &_Y) return *this;
		reset(_Y.release());
		//if (_Ptr != nullptr) { _myDeletor(_Ptr); }
		//_Ptr = _Y._Ptr;
		//_Y._Ptr = nullptr;
		cout << " move opertor=: " << endl;
		return *this;
	}
	_Dx & get_deleter()  
	{
		return _myDeletor;
	}
	const _Dx& get_deleter() const
	{
		return _myDeletor;
	}
	_Ty& operator*() const //重载*
	{
		return *_Ptr;
	}

	pointer operator->() const
	{
		return &**this;// _Ptr; 
	}
	pointer get() const
	{
		return _Ptr;
	}
	operator bool() const //强转
	{
		return _Ptr != nullptr;
	}
	pointer release() //释放
	{
		_Ty* old = _Ptr;
		_Ptr = nullptr;
		return old;
	}
	void reset(pointer _P = nullptr)
	{
		pointer old = _Ptr;
		_Ptr = _P;
		if (old != nullptr)
		{
			_myDeletor(old);
		}
	}
	void swap(my_unique_ptr _Y)
	{
		std::swap(_Ptr, _Y._Ptr);
		std::swap(_myDeletor, _Y._myDeletor);
	}
};

shared_ptr(共享指针)

  • 引用计数指针,用于共享对象的所有权,允许多个指针指向同一个对象
  • shared_ptr使用‘引用计数’方法来管理对象资源,每个shared_ptr对象关联一个共享的引用计数
  • shared_ptr在拷贝或赋值时,引用计数加1,当被赋予一个新值或被销毁时,引用计数减1,当计数器变为0,他就会自动释放自己所管理的对象
  • 计数器的值不存储在shared_ptr内,存储在堆上。
  • p=q;p的引用计数值会减少,q的引用计数值会增加
class Object {
private:
	int value;
public:
	Object(int x = 0)
		:value(x)
	{
		cout << "create Object" << endl;
	}
	~Object() { cout << "destory Object" << endl; }
	int& Value() { return value; }
	const int& Value()const { return value; }

};

int main()
{
	shared_ptr<Object> obj(new Object(100));
	cout << (*obj).Value() << endl;  
	cout << "obj引用计数" << obj.use_count() << endl;
	shared_ptr<Object> obj2 = obj;
	cout << "obj引用计数" << obj.use_count() << endl;
	cout << "obj2引用计数" << obj2.use_count() << endl;
	 //1 2 2 
	return 0;
}

在这里插入图片描述

创建

两种方法

  1. 通过自己的构造函数
    shared_ptr由一块内存的原生指针创建的时候(原生内存:Object还没有其他shared_ptr指向这块内存),计数器随着产生,会一直存在,直至所有的shared_ptr和weak_ptr都被销毁的时候。
    当所有的shared_ptr都被销毁时,这块内存就已经被释放了,但可能weak_ptr还存在导致计数器的释放可能发生在内存对象销毁后很久才发生。
    o
    在堆上实际发生了两次内存分配
Object ptr = new Object(10);
std:shared_ptr<Object> pObj(ptr);//分配内存给shared_ptr 的计数器
std:.shared_ptr< Object> pObj(new Object(10));

注意:同意普通指针不能同时为多个shared_ptr对象赋值,否则会导致程序发生异常

int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误
  1. 通过std::make_shared()
    可以同时为计数器和原生内存分配内存空间,并把二者的内存是坐整体管理

在这里插入图片描述

优点缺点
减少单词内存分配的次数,可以增大cache局部性有可能被管理的对象已经被析构了,但是他的空间还在,内存没有归还,他仍在等在weak_ptr都被清除之后才和计数器的空间一起被归还

在这里插入图片描述

检查引用计数

两个函数用来检查共享的引用计数值
1.unique():测试该shared_ptr是否是原始指针唯一拥有者,use_count() = 1时返回TRUE;否则false

2.use_count()返回当前指针的引用计数值,效率低,只用来测试或调试

线程安全性

1.引用计数本身是线程安全(引用计数是原子操作)
2.多个线程同时读同一个shared_ptr对象时线程安全的
3.多个线程同时对同一个shared_ptr对象进行读和写,则需要加锁

数据结构:堆上存放计数值

在这里插入图片描述

weak_ptr(弱共享)

  • weak_ptr是为了解决shared_ptr交叉引用的问题
class B;
struct A{
    shared_ptr<B> b;
};
struct B{
    shared_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;
//存在交叉引用,导致pa,pb都无法被正常释放

通过weak_ptr解决之后,可以打破shared_ptr的循环引用

class B;
struct A{
    shared_ptr<B> b;
};
struct B{
    weak_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;
  • 为配合shared_ptr而引入弱智能指针,指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数
  • weak_ptr并不拥有对动态对象的管辖权
创建

需要用一个shared_ptr实例来初始化weak_ptr

int main()
{
	shared_ptr<int> sp(new int(5));
	cout << "创建前sp的引用计数:" << sp.use_count() << endl; // use_count = 1
	weak_ptr<int> wp(sp);
	cout << "创建后sp的引用计数:" << sp.use_count() << endl; // use_count = 1
	return 0;
}

如何判断weak_ptr指向对象是否存在
  • 由于weak ptr并不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况。这时,我们就不能使用weak_ptr直接访问对象。
  • 在判断weak_ptr指向的对象是否存在时,C++提供了lock 函数来将其提升为强智能指针。
    如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr
class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x) { cout << "creat Object" << endl; }
	~Object() { cout << "deatory Object" << endl; }
	int GetValue() {
		return value;
	}
};
int main()
{
	shared_ptr<Object> sp(new Object(5));//动态开辟 用5初始化
	weak_ptr<Object> wp(sp);   //用shared_ptr实例初始化weak_ptr
	if (shared_ptr<Object> pa = wp.lock())
	{
		cout << pa->GetValue() << endl;
	}
	else
	{
		cout << wp.expired() << endl;
		cout << "wp所指对象为空" << endl;
	}
		return 0;
}
使用weak_ptr

不能直接通过weak_ptr直接访问对象,可以通过lock()获得shared_ptr,进而访问原始对象

在这里插入图片描述
在这里插入图片描述

Boost智能指针

shared_ptr< T >有一个引用计数器,表示T的对象是否不再使用
scoped_ptr< T >当离开作用域能够自动释放的指针,不传递所有权
intrusive_ptr< T >提供自己的指针使用引用计数机制
shared_array< T >用来处理数组
scoped_array< T >用类处理数组的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值