1、为什么需要智能指针
#include <iostream>
using namespace std;
// C/C++
bool Test1()
{
// 成功返回true,失败返回false
return false;
}
void Test2()
{
// ..
// 假设此处遇到非法情况
throw 1;
}
// 如果采用原生态指针(T*)管理资源,程序存在资源泄漏的风险就比较大
void TestFunc()
{
int* p = new int[10];
FILE* pf = fopen("2222.txt", "rb");
if (nullptr == pf)
{
delete[] p;
return;
}
// ...
if (!Test1())
{
delete[] p;
fclose(pf);
return;
}
try
{
Test2();
}
catch (...)
{
delete[] p;
fclose(pf);
throw;
}
// ...
delete[] p;
fclose(pf);
}
问题分析:上面的问题分析出来我们发现有以下两个问题?
1.存在内存泄漏的问题。
2. 异常安全问题。
我们在编写程序时必需要时刻注意这俩个问题,稍不留心,便会造成内存泄漏。
那能否让程序自我感知:对象在销毁时自动释放资源?
2、智能指针的使用及原理
C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象.
在构造函数中放资源
在析构函数中释放资源
这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效
3.1 智能指针的原理
template<class T>
class SmartPtr
{
public:
// RAII: 作用--->用户不用考虑什么是该释放资源
// 把释放资源的实际交给编译器
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{
}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct A
{
int a;
int b;
int c;
};
void TestSmartPtr()
{
SmartPtr<int> sp1(new int);
*sp1 = 10;
SmartPtr<A> sp2(new A);
sp2->a = 10;
sp2->b = 20;
sp2->c = 30;
//SmartPtr<A> sp3(new A); // 浅拷贝
}
/缺陷: 浅拷贝
c++98 ---- auto_ptr
解决浅拷贝方式:资源转移—>当前对象
即总是让最新的指针指向资源,原指针与资源断开联系
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr; // 资源转移
}
auto_ptr<T> operator=(const auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
private:
T* _ptr;
};
void TestAutoPtr()
{
auto_ptr<int> ap1(new int);
auto_ptr<int> ap2(ap1);
*ap1 = 10; 会产生新的问题 , ap1 无法在使用
}
产生新问题, 所以建议不要使用auto_ptr;
C++11:提供更靠谱的智能指针unique_ptr , 对 auto_ptr—>保留:资源转移实现的
unique_ptr 智能指针原理:
RAII(自动释放资源) + operator*()/operator->()(具有指针类似行为) + 提供解决浅拷贝方式
浅拷贝引起原因:默认拷贝构造函数 和 默认的赋值运算符重载
unique_ptr ----> 不让拷贝和赋值
template<class T,class DF = DFDef<T>>
class unique_ptr
{
public:
// RAII
unique_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
delete _ptr; // 释放资源的方式固定死了,只能管理new的资源,不能处理任意类型的资源
_ptr = nullptr;
}
}
// 具有指针类似行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// 解决浅拷贝:禁止调用拷贝构造函数和赋值运算符重载
// C++98:
/*推荐
private: // 拷贝构造的权限一定要设置成private,原因:为了防止用户自己在类外定义
unique_ptr(const unique_ptr<T>& up);
unique_ptr<T>& operator=(const unique_ptr<T>&);
*/
// C++11 禁止调用拷贝构造和赋值运算符重载
// 1. 释放new的空间 2.默认成员函数 = delete : 告诉编译器,删除该默认成员函数
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
private:
T* _ptr;
};
// 用户在外部实现
// template<class T>
// unique_ptr<T>::unique_ptr(const unique_ptr<T>& up)
// {}
void TestUniquePtr()
{
unique_ptr<int> up1(new int);
//unique_ptr<int> up2(up1);
unique_ptr<int> up3(new int);
//up3 = up1;
}
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pCount(nullptr)
{
if (_ptr) //若 _ptr 不为空
_pCount = new int(1);
}
~shared_ptr()
{
// 若有资源 且 当前对象为最后一个使用资源的对象 ,可释放资源
if (_ptr && 0 == --*_pCount)
{
delete _ptr;
delete _pCount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr) // 共享同一份资源
, _pCount(sp._pCount) // 共享同一份计数
{
if (_ptr)// 必须保证 _ptr 是有资源的 _ptr != nullptr
++*_pCount;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this != &sp)
{
// 1. 与旧资源断开联系
// this现在不是用自己的资源,要与sp对象共享资源
if (_ptr && 0 == --*_pCount)
{
// 当前对象是最后一个使用资源的对象
delete _ptr;
delete _pCount;
}
// 2. 与sp共享资源及计数
_ptr = sp._ptr;
_pCount = sp._pCount;
if (_ptr)//判断是否有资源
++*_pCount;
}
return *this;
}
int use_count()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};