发布时间:2022-12-02 文章分类:编程知识 投稿人:王小丽 字号: 默认 | | 超大 打印

代码1

int main(){
   //裸指针,手动开辟,需要自己释放,如果忘记了或者因为
   //程序逻辑导致p没有释放,那么就会导致内存泄漏
   int *p=new int(10);
   if(***){
     retur -1;
   }
   delete p;
   return 0;
}

有没有什么办法帮我们管理指针,确保资源释放?
智能指针
利用栈上的对象出作用域时自动析构的特征,来做到资源的自动释放
问题:是否可以在堆上创建裸指针?语法上没有问题,但是我们正式希望
栈上对象出作用域能自动析构的特征来达到自动管理指针的目的,如果
将智能指针创建在堆上,那又和原来的裸指针使用遇到的问题是一样的了
需要手动delete

代码2

#include <iostream>
using namespace std;
template<typename T>
class MySmartPtr1 {
public:
     MySmartPtr1(T * ptr=nullptr) : _mptr(ptr) {  }
	 ~MySmartPtr1() {
		 delete _mptr;
		 _mptr = nullptr;
	 }
	 T & operator*() { return *_mptr; }//返回的 是 & , 需要修改值
	 T * operator->() { return _mptr; }
private:
	T * _mptr;
};
int main() {
	MySmartPtr1<int> ptr(new int(10));
	*ptr= 200;
	return 0;
}

代码2的问题

int main() {
	MySmartPtr1<int> ptr(new int(10));
       //使用ptr 拷贝构造ptr2,默认的拷贝构造方式是值拷贝,所以底层
       //_mptr指针 指向的是同一块内存,那么ptr2 和ptr析构的时候就会有问题了,两次析构同一片内存       
        MySmartPtr1<int> ptr2(ptr); 
	*mptr = 200;
	return 0;
}

如何解决呢?
1:不带引用计数的智能指针
auto_ptr C++库提供
C++11 新标准
scoped_ptr
unique_ptr

代码 关于 auto_ptr

int main() {
	auto_ptr<int> ptr1(new int(100));
	auto_ptr<int> ptr2(ptr1);
	*ptr2 = 200;     
        cout<<*ptr1<<endl;//执行报错,原因见下图
	return 0;
}

<一>智能指针基础

现在不推荐使用auto_ptr
容器中推荐用auto_ptr吗? vector<auto_ptr> v1; v2(v1); 容器的拷贝构造和容器的赋值容易引起容器元素的拷贝构造和赋值,而auto_ptr的拷贝构造会将原来管理的底层资源(指针)置空

代码关于 scoped_ptr

int main() {
        scope_ptr的处理方式
        scope_ptr<int>(const scope_ptr<int> & src)=delete;//通过直接和谐掉这两个方法
        scope_ptr<int> & operator=(const scope_ptr<int> & src))=delete;//通过直接和谐掉这两个方法
        return 0;
}
所以scoped_ptr使用的也很少

代码关于 unique_ptr

int main() {
        unique_ptr的处理方式
        unique_ptr<int>(const unique_ptr<int> & src)=delete;//通过直接和谐掉这两个方法
        unique_ptr<int> & operator=(const unique_ptr<int> & src))=delete;//通过直接和谐掉这两个方法
        unique_ptr<int> ptr1(new int(100));
	unique_ptr<int> ptr2(ptr1);//左值的拷贝构造和赋值函数已经被屏蔽了,所以编译报错,"尝试使用已经删除的函数", 要改成如下!!!
        unique_ptr<int> ptr1(new int(100));	
        unique_ptr<int> ptr2(std::move(ptr1));//编译OK,为什么可以呢?因为unique_ptr提供了右值引用的拷贝构造和右值引用的赋值函数,如下
        unique_ptr<int>(const unique_ptr<int> && src){};
        unique_ptr<int> & operator=(const unique_ptr<int> && src)){};
        return 0;
}
//推荐使用

2:带引用计数的智能指针(share_ptr,weak_ptr)

带引用计数的好处:多个智能指针可以管理同一个资源
带引用计数:给每一个对象资源,匹配一个引用计数,
智能指针引用一个资源的时候,给这个资源引用计数加1
当这个智能指针出作用域不再使用资源的时候,给这个资源引用计数-1,当引用计数不为0的时候,还不能析构这个资源,
当引用计数为0的时候,说明已经没有外部资源使用这个资源了,那么就可以析构这个资源了

代码3 简单实现share_ptr

#include <iostream>
using namespace std;
template<typename T>
class RefCount {
public:
	RefCount(T * pSrc = nullptr, int refCount = 0):_pSrc(pSrc),_refCount(refCount) {
	}
	void addCount() { this->_refCount++; }
	void deleltCount() { --this->_refCount; }
	int  refCount() { return this->_refCount; }
private:
	T * _pSrc;
	int _refCount = 0;
};
template<typename T>
class MySmartPtr2 {
public:
	//新创建的智能指针,默认计数器为1
	MySmartPtr2<T> (T * mptr=nullptr): _mptr(mptr){
		_pRef = new RefCount<T>(_mptr,1);
	}
	//拷贝构造
	MySmartPtr2<T>(const MySmartPtr2<T> & _rval) {
		//两个智能指针指向相同的资源
		this->_mptr = _rval._mptr;
		this->_pRef = _rval._pRef;
		this->_pRef->addCount();	
	}
	//赋值重载
	MySmartPtr2<T> & operator=(const MySmartPtr2<T> & _rval) {
		if (this == &_rval) { retur *this; }
		else {
			this->_pRef->deleltCount();
			int currentCount = this->_pRef->refCount();
			if (currentCount == 0) {
				delete this->_mptr;//销毁指向的资源
				this->_mptr = nullptr;
				delete _pRef;
				_rPef = nullptr;
			}
			this->_pRef = _rval._pRef;
			this->_mptr = _rval._mptr;
			this->_pRef->addCount();
			return *this;
		}
	}
	~MySmartPtr2<T>() {
		this->_pRef->deleltCount();
		int currentCount = this->_pRef->refCount();
		if (currentCount == 0) {
			delete this->_mptr;//销毁指向的资源
			this->_mptr = nullptr;
			delete _pRef;
			_pRef = nullptr;
		}
	}
	int getRefCount() { return this->_pRef->refCount(); }
private:
	T * _mptr;
	RefCount<T> * _pRef;
};
int main() {
	MySmartPtr2<int> ms1(new int(100)) ;
	{
		MySmartPtr2<int> ms2(ms1);
		cout << "RefCount=" << ms1.getRefCount() << endl;
		MySmartPtr2<int> ms3(ms1);
		cout << "RefCount=" << ms1.getRefCount() << endl;
	}
	cout << "RefCount=" << ms1.getRefCount() << endl;
	system("pause");
	return 0;
}

share_ptr: 强智能指针,可以改变资源的引用计数
weak_ptr: 弱智能指针,不会改变资源的引用计数

强智能指针:循环引用(交叉引用)是什么问题?什么结果?怎么解决?

交叉引用代码

class A{
pubic:
    A(){cout<<"A()"<<endl;}
    ~A(){cou<<"~A()"<<endl;}
    share_ptr<B> _ptrb;
}
class B{
pubic:
    B(){cout<<"B()"<<endl;}
    ~B(){cou<<"~B()"<<endl;}
    share_ptr<A> _ptrb;
}
int main(){
     share_ptr<A> pa(new A());
     share_ptr<B> pb(new B());
     pa->_ptrb=pb;
     pb->_ptra=pa;
     cout<<pa.use_count()<<endl;// 2
     cout<<pb.use_count()<<endl;// 2
}

<一>智能指针基础

出main函数的时候,
第1步 先析构pb,pb在析构的时候,发现除了自己引用B对象,其他地方(A对象内)还有引用B对象的,所以 B资源无法释放
第2步,再析构pa,pa在析构的时候,发现除了自己引用A对象,其他地方(B对象内)还有引用A对象的,所以 导致A对象也无法释放

出了函数这两堆上的资源都没有被释放掉,泄漏!

上面代码造成new出来的资源无法释放!!资源泄漏问题

解决:
定义对象的时候,用强智能指针,引用对象的地方用弱智能指针

class A{
pubic:
    A(){cout<<"A()"<<endl;}
    ~A(){cou<<"~A()"<<endl;}
    void testA(){
        cout<<"A testA() Function"<<endl;
    }
    weak_ptr<B> _ptrb;
}
class B{
pubic:
    B(){cout<<"B()"<<endl;}
    ~B(){cou<<"~B()"<<endl;}
    void function(){
         share_ptr<A> _tp=_ptrb.lock();//提升方法
         if(_tp!=nullptr){
             _tp->testA();
         }         
    }
    weak_ptr<A> _ptrb; //weak_ptr 弱智能指针,不会改变引用计数
}
int main(){
     share_ptr<A> pa(new A());
     share_ptr<B> pb(new B());
     pa->_ptrb=pb;
     pb->_ptra=pa;
     pb.function();
     cout<<pa.use_count()<<endl;// 2
     cout<<pb.use_count()<<endl;// 2
}

share_ptr
share_ptr是C++11新添加的智能指针,它限定的资源可以被多个指针共享。

只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针

weak_ptr
weak_ptr是一种用于解决shared_ptr相互引用时产生死锁问题的智能指针。如果有两个shared_ptr相互引用,那么这两个shared_ptr指针的引用计数永远不会下降为0,资源永远不会释放。weak_ptr是对对象的一种弱引用,它不会增加对象的use_count,weak_ptr和shared_ptr可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr也可以通过调用lock函数来获得shared_ptr。

weak_ptr指针通常不单独使用,只能和 shared_ptr 类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。

share_ptr 和 weak_ptr 是线程安全的.