智能指针
为什么用智能指针?
智能指针是为了解决内存泄漏的问题。
在C语言中,我们用malloc申请内存,free释放内存;在C++中,既可以使用上述,对于自定义类型常常会用new申请,delete释放。一旦申请,忘了释放,就会导致内存泄漏。
即便有的时候,delete了,但是由于中间某些操作会异常,导致无法delete,也会造成内存泄漏。
所以引入了智能指针,能最大程度避免内存泄漏又能兼顾效率。
1.1 什么是内存泄漏?
是由于疏忽或错误导致程序未能释放已经不再使用的内存的情况。并不是指内存在物理上的消失,而是在应用程序分配某段内存后,因为设计错误,而失去了对该段内存的控制,因而造成了内存泄漏。
1.2 内存泄漏的危害
长期运行的程序出现内存泄漏,如操作系统、后台服务等,会导致响应越来越慢,最终卡死。
1.3 内存泄漏的分类
- 堆内存泄漏(heap leak)
堆内存泄漏指的是通过malloc / calloc / realloc / new 等从堆中分配的一块内存,在使用完成以后,没有使用free / delete 删除。假设因为程序的设计错误导致这块内存没有被删掉,就会导致以后无法使用,就会造成堆内存泄漏。
- 系统资源泄漏
指程序使用系统分配的资源,如套接字、文件描述符、管道没有使用对应的函数释放,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
1.4 如何避免内存泄漏
- 良好的编码规范,动态申请的内存记着要去释放。
- 采用RALL思想或者使用智能指针来管理资源。
- 出问题了使用内存泄漏检测工具。
1.4 内存泄漏与内存溢出
- 内存泄漏(momory leak):
是指程序在申请新的内存空间后,没有释放已经申请的内存空间,后果也许会造成内存溢出。
- 内存溢出(out of memory):
指程序申请内存时,没有足够的内存提供给申请者。内存不够用。
- 二者的关系
- 内存泄漏最终会导致内存溢出。
- 内存溢出就是你要的内存空间超过了系统能给你分配的空间,系统无法满足你的要求,就会报内存溢出的错误。
- 内存泄漏是指向系统动态申请的内存,用完以后不归还,结果你申请的那块空间也不能再次使用了。
- 内存溢出比如说栈,当栈满的时候再进栈必定会内存溢出,叫上溢。当栈空的时候还要出栈叫下溢。
-
内存溢出的原因及解决
- 原因
-
内存加载的数据量过于庞大,如一次从数据库取出过多数据。
-
代码中存在死循环或者循环过多重复的实体对象
-
使用第三方软件中的BUG
-
启动参数内存值设定的过小
- 解决
-
检查代码中是否有死循环或递归调用
-
检查是否有大循环重复产生新对象实体
-
检查对数据库查询中,是否有一次性获取全部数据。
2. 智能指针的原理
智能指针的作用:可以帮助我们避免在申请内存空间后忘记释放造成内存泄漏的问题。因为智能指针本身是一个类,当出了类的作用域,类会调用析构函数进行释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间。
2.1 RAII
(Resource Acquisition Is Initialization)资源获取即初始化。
在类的构造函数中申请资源并使用,最后在析构函数中释放资源。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
private:
T* _ptr;
};
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
// 讲tmp指针委托给了sp对象
SmartPtr<int> sp(tmp);
// _MergeSort(a, 0, n - 1, tmp);
// 这里假设处理了一些其他逻辑
vector<int> v(1000000000, 10);
// ...
}
int main()
{
try {
int a[5] = { 4, 5, 2, 3, 1 };
MergeSort(a, 5);
}
catch(const exception& e)
{
cout<<e.what()<<endl;
}
return 0;
}
2.2 具有指针的行为
上述的smartptr还不能将其成为智能指针,因为还不具备指针的行为。指针可以解引用,也可以通过 -> 去访问所知空间的内容。所以需要对* 、-> 进行重载。
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
struct Date
{
int _year;
int _month;
int _day;
};
int main()
{
SmartPtr<int> sp1(new int);
*sp1 = 10
cout<<*sp1<<endl;
SmartPtr<int> sparray(new Date);
// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
}
总结原理:
RAII特性 + 重载operator* 和 operator -> ,具有像指针一样的行为。
3. 智能指针的使用
包含头文件:
#include < memory>
3.1 shared_ptr
这里的指针主要指的是shared_ptr
,是一种引用计数型智能指针。可以记录内存中有多少个智能指针被引用,新增一个引用计数+ 1,过期一个引用计数 - 1,当引用计数为 0 的时候 , 智能指针才会释放资源。
通过引用计数的方式实现多个shared_ptr对象之间的共享资源。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A(int count)
{
_nCount = count;
}
~A(){}
void Print()
{
cout<<"count:"<<_nCount<<endl;
}
private:
int _nCount;
};
int main()
{
shared_ptr<A>p(new A(10));//初始化
p->Print();
return 0;
}
3.2 循环引用
循环引用计数:两个智能指针互相指向对方,造成内存泄漏。需要weak_ptr,将其中的一个指针设置为weak_ptr。
因为weak_ptr没有共享资源,它的构造函数不会引起智能指针引用计数的变化。
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数
3.3 智能指针的实现
#include<iostream>
#include<momory>
template<typename T>
class SmartPointer
{
private:
T* _ptr;
size_t* _count;
public:
SmartPointer(T* ptr = nullptr)
_ptr(ptr)
{
if (_ptr)
{
_count = new size_t(1);
}
else
{
_count = new size_t(0);
}
}
SmartPointer(const SmartPointer &ptr)
{
if (this != &ptr)
{
//共享管理新对象的资源,并增加引用计数
this->_ptr = ptr._ptr;
this - _count = ptr._count;
++(*this->_count);
}
}
SmartPointer& opreater = (const SmartPointer &ptr)
{
if (this->_ptr = ptr._ptr)
{
return *this;
}
if (this->_ptr)
{
--(*this->_count);
if (this->_count == 0)
{
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
++(*this->_count);
return *this;
}
T& operator*()
{
assert(this->_ptr == nullptr);
return *(this->_ptr);
}
T& operator->()
{
assert(this->_ptr == nullptr);
return this->_ptr;
}
~SmartPointer()
{
--(*this->_count);
if (0 == *this->_count)
{
delete this->ptr;
delete this->count;
}
}
size_t use_count()
{
return *this->_count;
}
};
int main()
{
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_cout() << std::endl;
}
3.2 其他智能指针
- unique_ptr
独占式智能指针,不允许拷贝复制,不允许拷贝构造。
是线程安全的。由于只是在当前代码块范围内生效, 因此不涉及线程安全问题
- weak_ptr
弱引用指针,用于观察shared_ptr或weak_ptr。用于解决循环引用 计数。
因为weak_ptr没有共享资源,它的构造函数不会引起智能指针引用计数的变化。