目录
使用场景
以下面程序为例,如果new时b抛了异常,那么会直接跳到主函数,虽然b没能成功申请空间,但a申请的空间资源就未能释放,造成内存泄漏,而给所有抛异常的都加上检查的话很麻烦,所以使用了智能指针来处理。
设计思路
RAII(Resource Acquisition Is Initialization):一种管理资源的类的设计思想,本质是一种利用对象生命周期来管理获取到的动态资源,避免资源泄露,这里的资源可以是内存、文件指针、网络连接、互斥锁等。
RAII在获取资源时将其委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构时释放资源,保证资源正确释放。
例:
//用智能指针对象托管new的指针,等到该对象生命周期结束就会将new出来的资源一起销毁
template<class T>
class Smartptr
{
T* _ptr;
public:
Smartptr(T* ptr)
:_ptr(ptr)
{}~Smartptr()
{
delete[] _ptr;
}T& operator*()
{
return *_ptr;
}T* operator->()
{
return _ptr;
}T& operator[](size_t i)
{
return _ptr[i];
}
};double Divide(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥使⽤RAII的智能指针类管理new出来的数组以后,程序简单多了
Smartptr<int> sp1 = new int[10];
Smartptr<int> sp2 = new int[10];for (size_t i = 0; i < 10; i++)
{
sp1[i] = sp2[i] = i;
}
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
C++标准库·智能指针
都包含在<memory> 这个头文件中
auto_ptr
是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是⼀个非常糟糕的设计,因为他会导致被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用auto_ptr。
unique_ptr
是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就非常适合使用。
shared_ptr
是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝, 也支持移动。如果需要拷贝的场景就需要使用他了,底层中使用了引用计数。
weak_ptr
是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指
针,他不支持RAII,也就意味着不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr的⼀个循环引用导致内存泄漏的问题。注:1.未重载operator*和operator->,因为它不参与资源管理。
2.支持使用expired检查指向的资源是否过期
3.可以调用lock返回一个管理资源的shared_ptr,如果资源已释放返回一个空shared_ptr对象。
补充
1.智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。
2.智能指针支持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。
3.shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared用初始化资源对象的值直接构造。
4.shared_ptr 和 unique_ptr 都支持了operator bool的类型转换,如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。
5.shared_ptr 和 unique_ptr 构造函数都使用了explicit修饰,防止普通指针隐式类型转换成智能指针对象。
explicit:用来修饰类的构造函数,以防止编译器进行隐式的类型转换或自动调用构造函数,从而避免潜在的错误或误解。
删除器
// 这样实现程序会崩溃 ,T为未实现析构的一个自定义类型
// unique_ptr<T> up1(new T[10]);
// shared_ptr<T> sp1(new T[10]);解决有两种方法:
1.因为new[]经常使⽤,所以unique_ptr和shared_ptr实现了⼀个特化版本,这个特化版本析构时⽤的delete[]
unique_ptr<T[]> up1(new T[5]);
shared_ptr<T[]> sp1(new T[5]);2.做删除器
注:
1.unique_ptr和shared_ptr⽀持删除器的⽅式有所不同,unique_ptr是在类模板参数⽀持的,shared_ptr是构造函数参数⽀持的
2.使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤,但是函数指针和lambda的类型不可以例:
template<class T>
class Deletetool
{
public:
void operator()(T* t)
{
delete[] t;
}
};template<class T>
void Deletefun(T* t)
{
delete[] t;
}class Date
{
int _year;
int _month;
public:
Date()
:_year(0)
,_month(0)
{ }
Date(int year,int month)
:_year(year)
,_month(month)
{}~Date()
{
;
}
};
int main()
{
//仿函数
unique_ptr<Date,Deletetool<Date>> up(new Date[10]);
shared_ptr<Date> sp(new Date[10], Deletetool<Date>());//函数指针
unique_ptr<Date,void(*)(Date*)> up2(new Date[10],Deletefun<Date>);
shared_ptr<Date> sp2(new Date[10], Deletefun<Date>);//lambda表达式
auto Deletelambda = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date,decltype(Deletelambda)> up3(new Date[10],Deletelambda);
shared_ptr<Date> sp3(new Date[10], Deletelambda)
return 0;
}
decltype
编译时推导表达式类型
循环引用
两个对象都有智能指针成员next和prev,当有智能指针先指向它们,并且它们相互指向时。
这时,就需要使用weak_ptr,将结点结构体中的next和prev指针由shared_ptr改为weak_ptr,让它们相互指向时,引用计数不会增加。
weak_ptr专门用来解决这类问题。
模拟实现
auto_ptr
template<class T>
class auto_ptr
{
T* _ptr;
public:
explicit 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>& p)
{
if (_ptr != p._ptr)
{
if (_ptr)
delete _ptr;
_ptr = p._ptr;
p._ptr = nullptr;
}
return *this;
}~auto_ptr()
{
if(_ptr)
delete _ptr;
}T& operator*()
{
return *_ptr;
}T* operator->()
{
return _ptr;
}
};
unique_ptr
template<class T>
class unique_ptr
{
T* _ptr;
public:
explicit unique_ptr(T* ptr)
:_ptr(ptr)
{}unique_ptr(unique_ptr<T>&& tt) noexcept
:_ptr(tt._ptr)
{
tt._ptr = nullptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& tt)
{
if (_ptr != tt._ptr)
{
if (_ptr)
delete _ptr;
_ptr = tt._ptr;
tt._ptr = nullptr;
}
return *this;
}
unique_ptr(const unique_ptr<T>& t) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& t) = delete;~unique_ptr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}T* operator->()
{
return _ptr;
}
};
shared_ptr
template<class T>
class shared_ptr
{
T* _ptr;
int* _count;function<void(T*)> _del = [](T* ptr) {delete ptr; };//function是functional头文件中的函数,使用时记得包头文件
public:
explicit shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_count(new int(1))
{}template<class D>
shared_ptr(T* ptr,D del)
:_ptr(ptr)
,_count(new int(1))
,_del(del)
{ }shared_ptr(const shared_ptr<T>& t)
{
_ptr = t._ptr;
_count = t._count;
(*_count)++;
}void release()
{
if (--(*_count) == 0)
{
_del(_ptr);
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}shared_ptr<T> operator=(const shared_ptr<T>& t)
{
if (_ptr != t._ptr)
{
release();
_ptr = t._ptr;
_count = t._count;
(*_count)++;
}
return *this;
}~shared_ptr()
{
release();
}T* get() const
{
return _ptr;
}int use_count() const
{
return *_count;
}T& operator*()
{
return *_ptr;
}T* operator->()
{
return _ptr;
}};
weak_ptr
template<class T>
class weak_ptr
{
T* _ptr;
public:
weak_ptr()
{}weak_ptr(const shared_ptr<T>& st)
:_ptr(st.get())
{}weak_ptr<T>& operator=(const shared_ptr<T>& st)
{
_ptr = st.get();
return *this;
}};
内存泄漏
内存泄漏:指因为疏忽或错误造成程序未能释放已经不在使用的内存,一般是忘记释放或发生异常释放的程序未能执行导致。
并不是指内存在物理上的消失,而是程序分配空间后,因为设计错误失去了对这段空间的控制,造成内存浪费。
内存泄漏的危害:普通程序运行⼀会就结束了出现内存泄漏问题也不大,进程正常结束,页表的映射关系解除,物理内存也可以释放。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务、长时间运行的客户端等等,不断出现内存泄漏会导致可用内存不断变少,各种功能响应越来越慢,最终卡死。
小知识
1.final可以修饰类和虚函数,表示该类/该虚函数不能被继承/重写
2.范围for在遍历时某些情况会将'\0‘也一起遍历。