RAII
RAII
机制就是一种自动管理资源的机制,其可以帮助程序员自动释放资源,来避免内存泄漏,C++中,智能指针就是基于RAII产生的。
内存泄漏的危害:
①如果程序申请的空间不需要使用之后,没有及时释放,就会引起内存泄漏,这一部分被申请的空间就会一直被占用,这部分资源就被浪费掉了
②如果程序运行的时间足够长,到最后就会无内存可用,最终程序就会挂掉,就算没有挂掉,程序也会越跑越慢,严重影响程序的稳定和性能
内存泄漏是 C++ 程序中一个非常常见的问题,如果不能及时发现并修复,会对程序的性能、稳定性和安全性造成严重影响。
简单应用:
#include<iostream>
#include<vector>
using namespace std;
template<class T>
class smart_ptr
{
public:
smart_ptr(T* p)
:_ptr(p)
{}
~smart_ptr()
{
delete _ptr;
cout << "delete finish" << endl;
}
private:
T* _ptr;
};
int main()
{
smart_ptr<int>p1 = new int(10);
smart_ptr<double>p2 = new double(3.14);
smart_ptr<vector<int>>p3 = new vector<int>(20);
return 0;
}
运行结果:
可以看到,申请了三分空间,我们并没有显示调用delete去清楚new出来的空间,但是smart_ptr自动调用析构函数,完成了空间释放,这样就可以避免内存空间的泄漏
对smart_ptr进行一下重载operator * 和operator->
T operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
智能指针
首先看上面smart_ptr存在的问题:
#include<iostream>
#include<vector>
using namespace std;
template<class T>
class smart_ptr
{
public:
smart_ptr(T* p)
:_ptr(p)
{}
~smart_ptr()
{
delete _ptr;
cout << "delete finish" << endl;
}
private:
T* _ptr;
};
int main()
{
smart_ptr<int>p1 = new int(10);
smart_ptr<int>p2 = p1;
return 0;
}
我们只需要对p1进行一次拷贝,就可以将程序挂掉,是因为,p1指针delete掉之后,p1成了野指针,我们再对p2进行delete必然会发生访问野指针
同一块空间被释放两次会将程序搞崩
接下来详细介绍 在C++库中为我们提供的智能指针的接口,就在<memory>头文件中:
#include<memory>
里边包含了各种智能指针,接下来介绍几种最常用的几种
auto_ptr
、unique_ptr
、shared_ptr
、weak_ptr
。
auto_ptr
auto_ptr 是C++中出现最早的智能指针,C++98中就存在了
构造函数 :
自带一个通过指针来进行初始化的构造函数,但是其被
explicit
修饰,这说明不允许进行类型转换:
可以看到这里不允许进行类型转换,但是可以进行直接构造,其实四种智能指针都不支持类型转换
拷贝构造:
#include<iostream>
#include<memory>
using namespace std;
int main()
{
auto_ptr<int>p1(new int(10));
auto_ptr<int>p2(p1);
auto_ptr<int>p3 = p2;//支持拷贝构造
return 0;
}
但是,如果进行拷贝,不就会发生上边提到的野指针现象吗
这里auto_ptr拷贝的时候,会将p1直接转移给p2,,而原来的p1成了空指针,p3拷贝p2也是同理
cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;
运行结果:
0000000000000000
0000000000000000
0000029604836EA0
这种方法过于简单粗暴,显然不足以满足开发需求,所以C++11又出现的三个指针
unique_ptr
unique_ptr 更加简单粗暴,直接不支持对对象进行拷贝
如果不需要进行拷贝,可以使用unique_ptr
unique_ptr对于auto_ptr来说,增加了访问[ ]的功能
int main()
{
auto_ptr<int> ptr1(new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
cout << ptr1[5] << endl;//错误
return 0;
}
int main()
{
unique_ptr<int[]> ptr1(new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
cout << ptr1[5] << endl;//错误
return 0;
}
shared_ptr
shared_ptr 是对拷贝处理的最好的指针,但是机制也是最复杂的,它是通过计数引用来实现的
对于同一块资源来说,有多少个shared_ptr指向这块空间,count 就为几,如果delete 一个shared_ptr count就会对应减一 ,知道count为零时,这块空间才会真正的被释放,这就避免了,同一块空间被释放多次的情况
对于不同的空间来说,count时独立的,一块空间会存在一个独立的count
shared_ptr也支持下标访问
底层实现:
template<class T>
class my_shared
{
public:
my_shared(T* p=nullptr)
:_ptr(nullptr)
,_count(new int(1))
{}
my_shared(const my_shared<T>& p)
{
_ptr = p._ptr;
_count = p._count;
++(*_count);
}
my_shared<T>& operator=(const my_shared<T>& p)
{
if (_ptr != p._ptr)
{
release();//本身指针指向发生变化,count--
_ptr = p._ptr;
_count = p._count;
++(*_count);
}
return *this;
}
int use_count()
{
return *_count;
}
void release()
{
if (--(*_count) == 0)//释放
{
delete _ptr;
delete _count;
}
}
T* operator->()
{
return _ptr;
}
T operator* ()
{
return *_ptr;
}
~my_shared()
{
release();
}
private:
T* _ptr;
int* _count;
};
这里重载operator= 和拷贝构造,
测试:
int main()
{
my_shared<int>p1 = new int(10);
cout << p1.use_count() << endl;//1
my_shared<int>p2(p1);
cout << p2.use_count() << endl;//2
my_shared<int>p3(p2);
cout << p3.use_count() << endl;//3
return 0;
}
shared_ptr其实有个大坑
struct ListNode
{
my_shared<ListNode> prev;
my_shared<ListNode> next;
int x;
ListNode(const int& val)
:prev(nullptr)
,next(nullptr)
,x(val)
{}
~ListNode()
{
cout << "Deletd finish" << endl;
}
};
int main()
{
my_shared<ListNode> p1 = new ListNode(10);
my_shared<ListNode> p2 = new ListNode(20);
p1->next = p2;
p2->prev = p1;
p1->prev = p2;
p2->next = p1;
return 0;
}
如果出现这种场景就会发生循环引用,程序就会迟迟不会正常释放p1 和p2
这是由于p1和p2的指针循环引用,各自的count 互相制约,都想要调用delete 但是count 始终不会变为1 ,所以这种情况可能发生时,prev 和 next 就可以不用shared_ptr 用普通的指针就可以
weak_ptr
为了优化shared_ptr 就有了weak_ptr
weak_ptr 有三种构造函数
①无参构造函数,此时weak_ptr为nullptr
②拷贝构造函数,用来拷贝其他weak_ptr
③由shared_ptr构造,此时shared_ptr 与weak_ptr指向同一块空间
第③种情况下,weak_ptr不会进行引用计数,weak_ptr离开作用域的时候,不会释放自己指向的资源,其只负责访问资源
模拟实现:
template<class T>
class my_weak
{
public:
my_weak()
:_ptr(nullptr)
{}
my_weak(const my_weak<T> p)
{
_ptr=p._ptr
}
my_weak<T> operator=(const shared_ptr<T>& p)
{
_ptr = p.get();
return *this;
}
T& operator()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};