一、智能指针的使用
还记得,在异常学习的时候,我们分析出了一个问题
double Divide(int x, int y)
{
if (y == 0)
{
throw string("the y is zero");
}
return (double)x / double(y);
}
void test(int x, int y)
{
int* arr = new int[10];
Divide(x, y);
delete[] arr;
cout << "delete[] arr" << endl;
}
int main()
{
while (1)
{
int x, y;
cin >> x >> y;
try
{
test(x, y);
}
catch (const string& str)
{
cout << str << endl;
}
catch (...)
{
cout << "unknown exception" << endl;
}
}
return 0;
}
在上述代码中,
Divide
函数,如果y==0
就会抛出异常,并且在该函数内没有捕获该异常,就继续将异常抛给外层调用的函数test
,在test
中new
了一个int
数组,但是没有捕获Divide
函数抛出的异常,程序直接接跳到main
函数当中去,就导致申请的空间资源没有被释放。
在异常学习中,我们的解决方法就是在test
函数中捕获Divide
函数抛出的异常,进行资源的释放再将异常重新抛出。
void test(int x, int y)
{
int* arr1 = new int[10];
try
{
Divide(x, y);
}
catch (...)
{
delete[] arr;
cout << "delete[] arr" << endl;
throw;
}
delete[] arr;
cout << "delete[] arr" << endl;
}
这样看起来我们似乎解决了这一个问题,但是如果我们开辟了两个数组呢?
void test(int x, int y)
{
int* arr1 = new int[10];
int* arr2 = new int[10];
try
{
Divide(x, y);
}
catch (...)
{
delete[] arr1;
cout << "delete[] arr1" << endl;
delete[] arr2;
cout << "delete[] arr2" << endl;
throw;
}
delete[] arr1;
cout << "delete[] arr1" << endl;
delete[] arr2;
cout << "delete[] arr2" << endl;
}
在上述代码中,如果我们在new
第二个数组时,new
抛异常了呢?(这里如果第一个new
抛异常,那没啥问题)
我们总不能再给第二个new
套一层try
吧,那不现实;如果申请了多个资源,每一个都要套上try
那太不现实了。
在当时我们也没有解决这一个问题,但是有了智能指针那就好很多了。
先来看一下什么是智能指针
二、RAII和智能指针
RAII
思想
RAII
是Resource Acquisition Is Initialization
的缩写,它是一个管理资源的类的设计思想;本质上就是利用对象生命周期来管理获取到的动态资源,避免发生内存泄露(这里资源指内存、文件指针、网络连接、互斥锁等等)RAII
思想:在获取到资源时把资源委托给一个对象,接着控制对资源的访问,这样资源在该对象的生命周期内始终是有效的,最后该对象生命周期结束,在析构的时候释放了资源;这样我们就保证了资源的正常释放,就可以结局上述资源泄露的问题。
那什么意思呢?
简单来说就是,我们不要去主动管理这些动态资源了,把这些动态资源交个一个对象去管理,这样出了这个对象的作用域,该对象的析构函数就会把这些动态资源自动释放,就不需要我们自己去释放了。
template<class T>
class smartptr
{
public:
smartptr(T* ptr)
:_ptr(ptr)
{}
~smartptr()
{
delete[] ptr;
cout << "delete []" << endl;
}
private:
T* _ptr;
};
有了上面代码,我们就可以创建smartptr
来帮助我们管理动态资源
void test(int x, int y)
{
smartptr<int> arr1(new int[10]);
smartptr<int> arr2(new int[10]);
Divide(x, y);
}
可以看到,无论Divide
是否抛异常,我们申请的资源都能成功释放(因为arr1
和arr2
出了作用域就调用析构函数,就会对资源进行释放)
智能指针思想
通过观察上述代码,我们可以发现一个问题:如何访问动态资源呢?
我们将动态资源交给一个对象去管理,是可以解决资源泄露的问题;但是我们如何去访问这个动态资源呢?
所以为了方便我们访问动态资源,智能指针就还要实现重载
operator*
、operator->
、opeartor[]
这些运算符这里这种思想就类似于迭代器,我们可以像指针一样去访问迭代器,这里智能指针也一样,我们也要可以像指针一样去访问智能指针。
template<class T>
class smartptr
{
public:
smartptr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
T operator->()
{
return _ptr;
}
~smartptr()
{
delete[] _ptr;
cout << "delete []" << endl;
}
private:
T* _ptr;
};
这里其实还存在一个致命的问题,那就是拷贝的问题:
对于我们自己申请的资源,我们就行拷贝(赋值)时,就是简单的值拷贝,并且释放的时候我们就只需要释放一个即可
但是如果使用智能指针,拷贝肯定不能使用深拷贝(我们想要的就是值拷贝),那我们该如何去释放这个资源呢?
**同一个资源是不能释放两次的;**现在来看
C++
库里面的智能指针是如何解决这一问题的。
三、C++
标准库中智能指针的使用
C++
标准库中智能指针都在这个头文件下;
智能指针有很多种,除了weak_ptr
以外都符合RAII
和可以像指针一样访问的行为,在原理上来说就是解决拷贝的问题不同。
auto_ptr
auto_ptr
:这是C++98
中设计出来的智能指针,它解决拷贝问题的方法就是在拷贝时将被拷贝对象资源的管理权转移给拷贝对象(简单来说就是,我把资源转给你,我自己置为nullptr
),这个可以说很糟糕,它会把被拷贝对象悬空,我们再访问就会报错;这里不推荐使用auto_ptr
。
这里为了观察就简单实现一个Date
类
struct Date
{
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
auto_ptr<Date> ap1(new Date);
auto_ptr<Date> ap2(ap1);//拷贝之后ap1被置为空
return 0;
}
~Date()
最后资源也是释放一次。
unique_ptr
unique_ptr
是C++11
设计出来的,它解决拷贝问题的方法就简单粗暴了,直接不支持拷贝,只支持移动;在不需要拷贝的场景下就推荐使用
unique_ptr
。通过看文档可以发现,它的拷贝构造和拷贝赋值是
delete
掉的。
int main()
{
unique_ptr<Date> up1(new Date);
//unique_ptr<Date> up2(up1);//不支持拷贝
//支持移动,但移动之后up1也置为空,使用要小心
unique_ptr<Date> up2(move(up1));
return 0;
}
shared_ptr
前面两个智能指针,一个拷贝是管理权转移,应该干脆就不支持,那还是没有到达我们想要的结果,现在来看shared_ptr
shared_ptr
是C++11
设计的智能指针,它支持拷贝,也支持移动;需要拷贝的场景就需要它了。(其底层使用引用计数来实现的)
int main()
{
shared_ptr<Date> sp1;
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp1);
//查看当前有多少对象管理这一资源
cout << sp1.use_count() << endl;
sp1->_year = 2025;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
return 0;
}
3
2025
2025
2025
~Date()
weak_ptr
也是C++11
设计的智能指针,它和上述智能指针不同,不支持RAII
它不能直接管理资源;
weak_ptr
实现本质上是为了解决shared_ptr
循环引用导致内存泄露的问题。
删除器
智能指针的析构默认是进行delete
来释放资源,那也就是说,如果我们将不是new
的资源交给智能指针,在析构的时候就会崩溃;
所以智能指针在构造时就支持给一个删除器。
本质上所谓的删除器就是一个可调用对象,这个可调用对象中实现我们需要是释放资源的方式;
当我们在构造智能指针时,给了删除器,在智能指针析构时就会调用删除器去释放资源。
而我们又经常使用
new[]
,所以unique_ptr
和shared_ptr
都特化了一个[]
的版本,我们在使用时只需类型给成T[]
即可;(例如:unique_ptr<Date[]> up1(new Date[10])
和shared_ptr<Date[]> sp1(new Date[10])
。
int main()
{
unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);
//删除器
//删除器是一个可调用对象,那我们就可以使用仿函数、函数指针、lambda来做删除器
//仿函数
unique_ptr<Date, DeleteArr<Date>> up2(new Date[3]);//类模版这里要传的是类型
shared_ptr<Date> sp2(new Date[3], DeleteArr<Date>());//构造函数这里我们要传对象
//函数指针
//这里类模版要传的是类型,而根据函数类型又没有办法得到函数,构造也要显示传递
unique_ptr<Date, void(*)(Date*)> up3(new Date[3],DeleteArrFunc<Date>);
shared_ptr<Date> sp3(new Date[3], DeleteArrFunc<Date>);
//lambda
auto del = [](Date* ptr) {delete[] ptr; };
//这里我们没办法指定lambda的类型,所以要先创建一个lambda对象然后使用decltype来推它的类型
unique_ptr<Date, decltype(del)> up4(new Date[3], del);
//shared_ptr就很好用了,只用在构造函数时传递即可
shared_ptr<Date> sp4(new Date[3], [](Date* ptr) {delete[] ptr; });
return 0;
}
这里
shared_ptr1
除了可以使用指向资源的指针构造,还可以使用make_shared
有初始化资源对象的值直接进行构造。
shared_ptr
和unique_ptr
都支持operator bool
的类型转换,如果智能指针对象是一个空的对象就返回false
;赋值返回true
。(这样我们就可以直接对智能指针对象进行判断是否为空)。
四、智能指针的实现原理
首先对于auto_ptr
,它的拷贝是管理权转移,感觉很不符合逻辑;而unique_ptr
是直接不支持拷贝,这两种智能指针实现起来还是非常简单,这里就不详细叙述了;
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& p)
:_ptr(p._ptr)
{
p._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (*this != ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
T* operator->()
{
return _ptr;
}
~auto_ptr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
unique_ptr(unique_ptr<T>& p) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& p) = delete;
unique_ptr(unique_ptr<T>&& p)
:_ptr(p._ptr)
{
p._ptr = nullptr;
}
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
share_ptr
实现原理
shared_ptr
使用了引用计数,简单来说呢,我们不光要在智能指针中记录要管理的资源,还要记录一下当前管理资源的智能指针的个数,所以这里我们需要一个引用计数;
那如何去实现这个引用计数呢?
这里首先肯定不能在类中存放一个值;
那静态成员变量是否可以呢?
显然是不可以的,因为静态成员变量是属于类的,我们想要的引用计数是和管理资源有关的,所以这里我们就只能存放
int*
,采用堆上动态开辟的方式,在构造的时候开辟一块空间,在拷贝的时候,将指针值拷贝给另一个对象,并且对值进+1
;那这样多个
shared_ptr
管理一块资源时,当析构的时候就--引用计数
,当引用计数减到0
时,表示当前析构的就是管理这一块资源的最后一个智能指针对象,就要析构资源。
OK呢,那现在就来简简单单手搓一个简易版
shared_ptr
出来
首先对于shared_ptr
的成员:T*
的指针、int*
的pcount
引用计数;
简单的operator*
、operator->
、operator[]
这些就不解释了,直接看代码:
template<class T>
class shared_ptr
{
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
};
构造函数
对于构造函数,首先就是默认构造,我们直接将_ptr
和_pcount
赋值为nullptr
即可。
然后就是:我们在创建一个shared_ptr
的智能指针对象时,要做的就是开辟一块引用计数的空间,并赋值成1
(表示当前有一个对象管理这一块资源);然后把传过来的一块资源赋值给_ptr
(让ptr
指向要管理的资源即可)。
shared_ptr()
:_ptr(nullptr)
, _pcount(nullptr)
{}
shared_ptr(T* ptr)
:_ptr(ptr)
{
_pcount = new int(1);
}
拷贝构造
对于拷贝构造,当我们调用拷贝构造时,就表明我们要将一个智能指针对象管理的资源共享给另一个智能指针对象,此时我们的引用计数要进行+1
。
这里因为我们调用拷贝构造时,我们当前对象是没有管理任何资源的(
_ptr
和pcount
都为nullptr
),我们才能直接将被拷贝对象sp
的_ptr
和_pcount
直接赋值给我们*this
的_ptr
和_pcount
。
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
(*_pcount)++;
}
拷贝赋值
对于拷贝赋值,它拷贝构造那样,可以直接进行赋值操作;
当我们调用拷贝赋值时,我们当前对象可能是管理着其他资源的;所以我们要先将当前管理的资源进行处理(如果有其他对象管理着这一块资源,那--引用计数
即可;如果没有其他对象管理这一块资源,我们还要对其进行释放)。
这一块对资源进行处理的操作,我们可以发现和析构函数的逻辑一样,所以我们可以将其单独写成一个函数
release
。
void release()
{
(*_pcount)--;
if (*_pcount == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = nullptr;
_pcount = nullptr;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
release();
_ptr = sp._ptr;
_pcount = sp._pcount;
(*_pcount)++;
return *this;
}
析构函数
对于析构函数,它的逻辑就是--引用计数
,如果引用计数减到0
,那就释放资源;(逻辑和上面拷贝赋值处理资源的逻辑一样)
这里就可以直接复用release
。
~shared_ptr()
{
release();
}
这里对于拷贝赋值和析构函数这里,博主还有一种想法:
我们在拷贝赋值的参数那里让他传值传参,这样就会调用一次拷贝构造,构造了一个临时对象
sp
;我们再让
this
指向的_ptr
和_pcount
和sp
进行一下交换,这样出了拷贝赋值函数,sp
会自动调用析构函数;这样我们只需要在析构函数内部实现处理资源的操作就OK了。
void swap(shared_ptr<T>& sp)
{
std::swap(_ptr, sp._ptr);
std::swap(_pcount, sp._pcount);
}
shared_ptr<T>& operator=(shared_ptr<T> sp)
{
swap(sp);
return *this;
}
~shared_ptr()
{
(*_pcount)--;
if (*_pcount == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = nullptr;
_pcount = nullptr;
}
上面这种方法,博主在
vector
模拟实现时有所耳闻,也是非常好理解的我们拷贝赋值的参数写的是
shared_ptr<T>
,这样在传参时是传值传参,就会去调用拷贝构造,构造一个新的对象sp
指向被调用对象管理的资源;而
sp
的作用域就是operator=
函数内,所以我们把this
的指向的对象和生成的形参对象sp
进行交换(值交换);这样出了作用域
sp
要调用析构函数,就会把*this
对象原来管理的资源进行处理;
到这里,简易版的shared_ptr
就实现完成了。
现在我们来加上删除器
删除器
删除器,我们要想在类中可以调用这个删除器,那我们要将删除器存下来;
shared_ptr
我们只需要在构造函数
中传递就可以了,那我们构造函数如下面所示
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr)
,_pcount(new int(1))
,_del(del)
{}
但是但是,对于这个类型
D
我们只在构造函数中指定它是什么啊,那在类中如何去存储这个删除器呢?真的要在模版参数那里多一个吗?库里面也没有多这一个模版参数啊.
这里就使用
function
包装器就可以解决问题了。
因为我们的删除器肯定都是没有返回值(void
),且参数肯定是T*
,所以使用function<void(T*)>
包装即可。
这里在展示代码之前,罗列几个要注意的点:
- 在拷贝赋值时,我们要将删除器一同传递过去;(不同的类型,删除器不一样)
- 在析构逻辑中,我们直接调用删除器去资源,但是对于引用计数的修改还是需要我们就行操作的。
- 删除器我们要给缺省值,当我们在构造函数不穿第二个参数时,我们默认的删除器是
delete
的。
template<class T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pcount(new int(1))
{}
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr)
,_pcount(new int(1))
,_del(del)
{}
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
(*_pcount)++;
}
void swap(shared_ptr<T>& sp)
{
std::swap(_ptr, sp._ptr);
std::swap(_pcount, sp._pcount);
std::swap(_del, sp._del);
}
shared_ptr<T>& operator=(shared_ptr<T> sp)
{
swap(sp);
return *this;
}
~shared_ptr()
{
(*_pcount)--;
if (*_pcount == 0)
{
//delete _ptr;
_del(_ptr);
delete _pcount;
}
_ptr = nullptr;
_pcount = nullptr;
}
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr) {delete ptr;};
};
五、shared_ptr
循环引用问题
对于
shared_ptr
大多数情况下已经可以去管理资源了,支持RAII
也支持拷贝;但是有一种特殊情况,
循环引用
的场景下,还是会遇到问题的;(会导致资源没得到释放)
struct ListNode
{
int _date;
std::shared_ptr<ListNode> _next;
std::shared_ptr<ListNode> _prve;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prve = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
return 0;
}
可以看到,在上述代码中,我们让n1->_next
指向n2
;n2->_prve
的_prve
指向n1
。
这样指向之后我们发现n1
和n2
的引用计数都变成了2
,并且知道程序结束,也没有释放资源。
这种情况,就是我们所说的内存泄露。
我们现在来分析一下,为什么会造成内存泄露呢?
如上图所示,n1->_next = n2
、n2->_next = n1
之后,n1
和n2
对应的引用计数都+1
,变成了2
。
那我们现在n1
、n2
调用析构:
n1
、n2
调用析构之后,其对应的引用计数-1
减到了1
,没有减到0
,这两块空间还没有释放;
那我们
右边
节点什么时候释放呢,左边节点中的_next
管理着,左边节点中_next
析构后,右边节点就释放了;那左边节点的
_next
什么时候释放呢?,那要等到左边节点析构,右边节点的_prve
管理着,等右边节点_prve
析构,左边节点就释放了。左边节点右边的
_prve
管理着,右边节点左边的_next
管理着,那这样都在等对方析构,而谁都不会释放,就形成了循环引用,从而导致内存泄露。
weak_ptr
解决循环引用问题
那这个问题如何解决呢?
要像解决这个问题,我们来看一下这个问题的本质是什么?
**那就是我们将
n1->_next
绑定n2
节点时,n2
节点的引用计数会+1
;将n2->_prve
绑定n1
节点时,n1
几点的引用计数会+1
。**这样就导致我们在析构n1
和n2
时,引用计数-1
之后不等于0
,就无法释放资源。简单来说,就是
n1->_next
和n2->_prve
参与了资源的管理。
c++11
还有一种智能指针weak_ptr
,它就是专门来解决这个问题的。
我们先来看一下weak_ptr
:
其实通过观察weak_ptr
的构造函数和赋值重载就可以发现,它支持使用shared_ptr
去构造和赋值;
但是它有一个特点,我们将shared_ptr
的智能指针对象赋值给weak_ptr
,我们shared_ptr
对象的计数引用不会变化(weak_ptr
不会参与shared_ptr
的管理资源)。
那这样,我们再看上述问题,我们将ListNode
结构体中_next
和_prve
的类型改成weak_ptr
;
那这样,将
n1->_next
绑定n2
节点时,n2
节点的引用计数不会+1
;将
n2->_prve
绑定n1
节点时,n1
节点的引用计数不会+1
;这样我们在析构
n1
和n2
时,引用计数-1
就等于0
,就会释放资源。
struct ListNode
{
int _date;
//std::shared_ptr<ListNode> _next;
//std::shared_ptr<ListNode> _prve;
std::weak_ptr<ListNode> _next;
std::weak_ptr<ListNode> _prve;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prve = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
return 0;
}
可以看到引用计数并没有
+1
,也成功析构,没有造成资源泄露。
weak_ptr
这里简单了解有效
weak_ptr
weak_ptr
不支持RAII
,也不支持访问资源,我们在文档中也会发现weak_ptr
构造没有支持绑定资源,而是支持绑定到shared_ptr
;在绑定到shared_ptr
时,不会增加shared_ptr
的引用计数。weak_ptr
没有重载operator*
和operator->
,它不参与资源管理(如果weak_ptr
绑定的shared_ptr
已经析构了,那如果再去访问就和危险);weak_ptr
支持了expired
检查指向的资源是否过期,use_count
也支持获取shared_ptr
的引用计数。weak_ptr
还支持了lock
,它可以返回shared_ptr
;如果资源已经释放,那返回的就是空对象;如果没有释放,那返回的shared_ptr
可以进行访问资源。
六、内存泄露
什么是内存泄漏?
这个问题之前就有所耳闻;内存泄漏指因为疏忽或者错误造成程序没有释放已经不再使用的内存,(右边就是忘记释放或者异常导致未能释放);
内存泄漏指的实际上是程序分配某部分内存以后,因为某种失误,导致失去了对这些内存的控制,造成了内存的浪费
内存泄漏的危害
在我们平常写代码过程中,我们感受不到内存泄漏带来的影响;因为我们写的普通程序,它运行一会就结束了;
进程正常结束,页表映射关系解除,物理内存也就释放了;
但是对于一些长期运行的内存,如果出现内存泄漏,那影响很大了,就好比
操作系统
、后台服务器
、长时间运行的客户端
;如果出现内存泄漏,就导致可用的内存不断减少,运行速度越来越慢,最后卡死。
现在已经支持一些对于内存泄露的检查
但是我们能够避免内存泄露还是尽量去避免的。
养成良好的习惯,申请的内存空间记得去释放;
如果遇到异常问题,可能我们无法彻底去解决,那就使用智能指针去解决
当然使用智能指针要注意避免循环引用的情况。
对于定期使用内存泄露工具检测,尤其是在一个项目上线之前。
简单总结
内存泄漏非常常见,我们尽可能的去避免;
- 在写代码是去预防:使用智能指针去管理资源;
- 事后查错:使用泄漏检测工具去定期检查。
七、shared_ptr
线程安全问题
这部分呢,在博主学习了
linux
线程之后,再来叙述。
八、Boost库
最后,我们来了解一下Boost
库
Boost
库是c++
语言标准库提供的扩展的一些C++
程序库,Boost
社区建立的初衷之一就是为了c++
标准化工作提供参考。
Boost
社区的发起人Dawes
本人就是C++
委员会的成员之一。在
Boost
库的开发中Boost
社区在这个方向上取得了丰硕的成果。
C++98
有了第一个智能指针auto_ptr
C++boost
库给出了更多实用的智能指针scoped_ptr/scoped_array和shared_ptr/shared_array/weak_ptr
等
C++ TR1
,引⼊了shared_ptr
等,不过注意的是TR1并不是标准版。
C++ 11
,引⼊了unique_ptr
和shared_ptr
和weak_ptr
。需要注意的是unique_ptr
对应boost的
漏,那影响很大了,就好比操作系统
、后台服务器
、长时间运行的客户端
;如果出现内存泄漏,就导致可用的内存不断减少,运行速度越来越慢,最后卡死。
现在已经支持一些对于内存泄露的检查
但是我们能够避免内存泄露还是尽量去避免的。
养成良好的习惯,申请的内存空间记得去释放;
如果遇到异常问题,可能我们无法彻底去解决,那就使用智能指针去解决
当然使用智能指针要注意避免循环引用的情况。
对于定期使用内存泄露工具检测,尤其是在一个项目上线之前。
简单总结
内存泄漏非常常见,我们尽可能的去避免;
- 在写代码是去预防:使用智能指针去管理资源;
- 事后查错:使用泄漏检测工具去定期检查。
到这里本篇关于智能指针的讲解就结束了,感谢各位的支持!!!
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws