一.为什么需要智能指针
学习C++的人,一直在接触裸指针,一边感受着它的强大,一边感受着它的坑爹。当然,坑不坑爹在于开发者,指针本身近乎完美,但奈何用的人比较猥琐,给自己埋下无数的坑,还哭喊着指针不好用,那么今天要介绍的智能指针可以释放大家在使用裸指针时的一些压力,当然智能指针无法替代裸指针的全部功能。
那么,我们在使用裸指针的时候会遇到哪些问题呢??
- 忘记释放资源,导致资源泄露(常发生内存泄漏问题)
- 同一资源释放多次,导致释放野指针,程序崩溃
- 明明代码的后面写了释放资源的代码,但是由于程序逻辑满足条件,从中间return掉了,导致释放资源的代码未被执行到,懵
- 代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到
总之,智能指针的智能二字,主要体现在用户可以不关注资源的释放,因为智能指针会帮你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在到期的情况下,一定会进行释放。
二.内存泄漏
2.1.什么是内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
2.2.内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。2.3.内存泄漏的分类
堆内存泄漏 (Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new 等从堆中分配的一 块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak 。 系统资源泄漏 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。2.4.如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。 ps : 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。 2. 采用 RAII 思想或者智能指针来管理资源。 3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。 4. 出问题了使用内存泄漏工具检测。 ps :不过很多工具都不够靠谱,或者收费昂贵。 总结一下 : 内存泄漏非常常见,解决方案分为两种:1 、事前预防型。如智能指针等。 2 、事后查错型。如泄漏检测工具。C++11库里面,提供了带引用计数的智能指针和不带引用计数的智能指针,这篇文章主要介绍它们的原理和应用场景,包括auto_ptr,scoped_ptr,unique_ptr,shared_ptr,weak_ptr。
三.智能指针
为了更好的理解C++库中智能指针的原理,我们首先需要自己实现一个简单的智能指针,窥探一下智能指针的基本原理,就是利用栈上的对象出作用域会自动析构这么一个特点,把资源释放的代码全部放在这个析构函数中执行,就达到了所谓的智能指针。对比下面的两块代码:
int main()
{
int *p = new int;
/*其它的代码...*/
/*
如果这里忘记写delete,或者上面的代码段中程序return掉了,
没有执行到这里,都会导致这里没有释放内存,内存泄漏
*/
delete p;
return 0;
}
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr) :mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
const T& operator*()const { return *mptr; }
T* operator->() { return mptr; }
const T* operator->()const { return mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> ptr(new int);
*ptr = 20;
cout << *ptr << endl;
return 0;
}
上面的这个智能指针,使用起来就和普通的裸指针非常相似了,但是它还存在很大的问题,看下面的代码:
int main()
{
CSmartPtr<int> ptr1(new int);
CSmartPtr<int> ptr2(ptr1);
return 0;
}
这个main函数运行,代码直接崩溃,问题出在默认的拷贝构造函数做的是浅拷贝,两个智能指针都持有一个new int资源,ptr2先析构释放了资源,到ptr1析构的时候,就成了delete野指针了,造成程序崩溃。所以这里引出来智能指针需要解决的两件事情:
怎么解决智能指针的浅拷贝问题
多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次,造成代码运行不可预期的严重后果
我们一起看看C++库中提供的智能指针是怎么解决上面提到的问题的。
3.1.不带引用计数的智能指针std::auto_ptr、std::unique_ptr
std::auto_ptr
先来看一下auto_ptr的部分源码:
template<class _Ty>
class auto_ptr
{ // wrap an object pointer to ensure destruction
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty * _Ptr = nullptr) noexcept
: _Myptr(_Ptr)
{ // construct from object pointer
}
/*这里是auto_ptr的拷贝构造函数,
_Right.release()函数中,把_Right的_Myptr
赋为nullptr,也就是换成当前auto_ptr持有资源地址
*/
auto_ptr(auto_ptr& _Right) noexcept
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
_Ty * release() noexcept
{ // return wrapped pointer and give up ownership
_Ty * _Tmp = _Myptr;
_Myptr = nullptr;
return (_Tmp);
}
private:
_Ty * _Myptr; // the wrapped object pointer
};
auto_ptr的构造函数是将原来的地址赋给新的指针,然后将原来的指针置为nullptr。
int main()
{
vector<auto_ptr<int>> vec;
vec.push_back(auto_ptr<int>(new int(10)));
vec.push_back(auto_ptr<int>(new int(20)));
vec.push_back(auto_ptr<int>(new int(30)));
// 这里可以打印出10
cout << *vec[0] << endl;
vector<auto_ptr<int>> vec2 = vec;
/* 这里由于上面做了vector容器的拷贝,相当于容器中
的每一个元素都进行了拷贝构造,原来vec中的智能指针
全部为nullptr了,再次访问就成访问空指针了,程序崩溃
*/
cout << *vec[0] << endl;
return 0;
}
上面的程序,如果我们不了解auto_ptr的实现,代码就会出现严重的问题。
auto_ptr智能指针不带引用计数,那么它处理浅拷贝的问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源。
std::unique_ptr
我们还是先来看看源码:
template<class _Ty,
class _Dx> // = default_delete<_Ty>
class unique_ptr
: public _Unique_ptr_base<_Ty, _Dx>
{ // non-copyable pointer to an object
public:
typedef _Unique_ptr_base<_Ty, _Dx> _Mybase;
typedef typename _Mybase::pointer pointer;
typedef _Ty element_type;
typedef _Dx deleter_type;
/*提供了右值引用的拷贝构造函数*/
unique_ptr(unique_ptr&& _Right) noexcept
: _Mybase(_Right.release(),
_STD forward<_Dx>(_Right.get_deleter()))
{ // construct by moving _Right
}
/*提供了右值引用的operator=赋值重载函数*/
unique_ptr& operator=(unique_ptr&& _Right) noexcept
{ // assign by moving _Right
if (this != _STD addressof(_Right))
{ // different, do the move
reset(_Right.release());
this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
}
return (*this);
}
/*
交换两个unique_ptr智能指针对象的底层指针
和删除器
*/
void swap(unique_ptr& _Right) noexcept
{ // swap elements
_Swap_adl(this->_Myptr(), _Right._Myptr());
_Swap_adl(this->get_deleter(), _Right.get_deleter());
}
/*通过自定义删除器释放资源*/
~unique_ptr() noexcept
{ // destroy the object
if (get() != pointer())
{
this->get_deleter()(get());
}
}
/*unique_ptr提供->运算符的重载函数*/
_NODISCARD pointer operator->() const noexcept
{ // return pointer to class object
return (this->_Myptr());
}
/*返回智能指针对象底层管理的指针*/
_NODISCARD pointer get() const noexcept
{ // return pointer to object
return (this->_Myptr());
}
/*提供bool类型的重载,使unique_ptr对象可以
直接使用在逻辑语句当中,比如if,for,while等*/
explicit operator bool() const noexcept
{ // test for non-null pointer
return (get() != pointer());
}
/*功能和auto_ptr的release函数功能相同,最终也是只有一个unique_ptr指针指向资源*/
pointer release() noexcept
{ // yield ownership of pointer
pointer _Ans = get();
this->_Myptr() = pointer();
return (_Ans);
}
/*把unique_ptr原来的旧资源释放,重置新的资源_Ptr*/
void reset(pointer _Ptr = pointer()) noexcept
{ // establish new pointer
pointer _Old = get();
this->_Myptr() = _Ptr;
if (_Old != pointer())
{
this->get_deleter()(_Old);
}
}
/*
删除了unique_ptr的拷贝构造和operator=赋值函数,
因此不能做unique_ptr智能指针对象的拷贝构造和
赋值,防止浅拷贝的发生
*/
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
unique_ptr去掉了拷贝构造函数和operator=赋值重载函数,禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。
但是unique_ptr提供了带右值引用参数的拷贝构造和赋值,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作,或者在产生unique_ptr临时对象的地方,如把unique_ptr作为函数的返回值时,示例代码如下:
// 示例1
unique_ptr<int> ptr(new int);
unique_ptr<int> ptr2 = std::move(ptr); // 使用了右值引用的拷贝构造
ptr2 = std::move(ptr); // 使用了右值引用的operator=赋值重载函数
使用std::move移动语义,将ptr转化为了右值引用,传入,调用了它的带右值引用的拷贝构造函数,也就是移动构造函数。
unique_ptr还提供了reset重置资源,swap交换资源等函数,也经常会使用到。可以看到,unique_ptr从名字就可以看出来,最终也是只能有一个该智能指针引用资源,因此建议在使用不带引用计数的智能指针时,可以优先选择unique_ptr智能指针。
3.2.带引用计数的智能指针std::shared_ptr和weak_ptr
shared_ptr:是一个强智能指针,可以改变资源的引用计数。
weak_ptr:是弱智能指针,不会改变资源的引用计数。
强智能指针循环引用是什么问题?什么结果?怎么解决?
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb;
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
出了main函数后,pa和pb指针会析构,但是我们类的引用计数从2变成了1,没有办法进行析构,从而导致了资源泄漏。
解决:
定义对象的时候,用强智能指针,引用对象的时候使用弱智能指针。即定义A,B类的时候使用弱智能指针。
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb;
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra;
};
我们再看一段代码:
class B;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法" << endl; }
weak_ptr<B> _ptrb;
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void func() { _ptra->testA(); }
weak_ptr<A> _ptra;
};
B类有个函数,想要去调用A类中的那个非常好用的方法,能否调用成功?
答案是不能的,weak_ptr没有重载operaot=,operator->,它相当于是一个观察者,不能去访问资源。但是我们想要调用这个方法也是可以的,我们需要对weak_ptr提升等级,提升后看那部分资源是否还在,不在的话返回的是nullptr。
void func()
{
shared_ptr<A> ps = _ptra.lock(); // 提升方法
if (ps != nullptr)
{
ps->testA();
}
}
这样我们就能访问到A类中那个非常好用的方法啦!
四.多线程访问共享对象的线程安全问题
C++ 非常著名的开源网络库muduo库
多线程访问共享对象的线程安全问题:
#include <thread>
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法" << endl; }
weak_ptr<B> _ptrb;
};
void handler01(weak_ptr<A> pw)
{
// q访问A对象的时候,需要侦测一下A对象是否存活
shared_ptr<A> ps = pw.lock();
if( ps != nullptr)
{
ps->testA();
}
else
{
cout << "A对象已经析构,无法访问" << endl;
}
}
// main线程
int main()
{
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
std::this_thread::sleep_for(std::chrono::seconds(2));
t1.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(20));
// 阻塞等待子线程结束
// t1.join();
return 0;
}
五.智能指针删除器
deletor
// 仿函数的删除器
template<class T>
struct FreeFunc {
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc {
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
int main()
{
FreeFunc<int> freeFunc;
std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
std::shared_ptr<A> sp4(new A[10], [](A* p){delete[] p; });
std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p)
{fclose(p); });
return 0;
}
标签:11,auto,C++,智能,unique,pointer,ptr,指针
From: https://blog.csdn.net/m0_73839343/article/details/143590718