目录
一、为什么要有智能指针
1.1内存泄露
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
1.2内存泄露的分类
1.3如何避免内存泄露
二、智能指针的使用及原理
2.1RALL
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
2.2智能指针的原理
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
SmartPtr<int> sp1(new int(0));
SmartPtr<int> sp2(new int(1));
*sp1 += 10;
SmartPtr<pair<string,int>> sp3(new pair<string,int>);
sp3->first = "apple";
sp3->second = 1;
sp3.operator->()->second = 2;//显示去调用operator->
cout << div() << endl;
}
int main()
{
try
{
Func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
2.3智能指针的拷贝问题
像类似于vector、list拷贝时都要进行深拷贝。利用资源存储管理数据,资源是自己的,拷贝是,每个对象各自一份资源,各管各的,所以进行深拷贝。
而智能指针/迭代器 都是浅拷贝。本质资源不是自己的,代为持有,方便访问修改数据。它们拷贝的时候指向同一个资源,所以浅拷贝。同时智能指针还要释放资源。
三、C++98:auto_ptr
针对上述智能指针的拷贝问题,库中的auto_ptr解决了(以一种百思不得其解的方式)。
#include<memory>
int main()
{
auto_ptr<int> sp1(new int(0));
auto_ptr<int> sp2(sp1);
return 0;
}
它拷贝的解决方法是:管理权转移,被拷贝对象把资源管理权转移给拷贝对象,如果在放在右值引用移动构造转移将亡值的情况下博主表示理解,毕竟那个值马上要亡了,转移就转移吧。可是此处转移完成后sp1直接就废掉了。(究竟是谁研究的小玩意,让这样的用法进入c++标准库?)
总结一下:
1、auto_ptr 管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。
2、注意拷贝过后不能访问被拷贝对象,否则就出现空指针了。很多公司禁止使用它,因为他巨坑。
3.1模拟实现
namespace gaz
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
//智能指针的拷贝
auto_ptr(auto_ptr<T>& sp)
{
_ptr = sp._ptr;
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
依旧是熟悉的配方:sp1sp3直接寄了
3.2boost库
为了避免再有如上的语法被直接载入C++标准库后委员会追悔莫及,boost库应运而生。类似于体验服。
Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称。
Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。在C++社区中影响甚大,是不折不扣的“准”标准库。
Boost由于其对跨平台的强调,对标准C++的强调,与编写平台无关。但Boost中也有很多是实验性质的东西,在实际的开发中使用需要谨慎。
boost库中就有一堆智能指针比如shared_ptr、scoped_ptr、intrusive_ptr。最后一个就没有被纳入标准库。
四、C++11中的智能指针
4.1unique_ptr
既然auto_ptr存在如此大的拷贝隐患,那unique_ptr就解决这个隐患:不允许拷贝,适用于不需要拷贝的场景。
我只能说:6
模拟实现和如上auto_ptr基本上一样,唯一不同就是将拷贝构造改成下面:
unique_ptr(const unique_ptr<T>& sp)=delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp)=delete;
如果需要拷贝:
4.2shared_ptr
允许自由拷贝,使用引用计数解决多次释放的问题。
引用计数:记录有几个对象参与管理这个资源。
注意:不能直接给shared_ptr类定一个静态成员变量用来引用计数,因为静态成员变量属于整个类 ,不是属于一个对象,如果使用静态成员变量来引用计数会导致内存泄露,被释放掉的只有最后一个对象。
一个资源配一个计数,无论多少哥对象管理这个资源,只有这一个计数,所以可以给每个对象存一个指向计数的指针。
直接上代码:
#pragma once
#include<iostream>
using namespace std;
namespace gaz
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
//智能指针的拷贝
shared_ptr(auto_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
//拷贝时++计数
++(*_pcount);
}
~shared_ptr()
{
//析构时,--计数,计数减到0说明最后一个管理对象析构了,可以释放资源
if (--(*_pcount)==0 )
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
int use_count()
{
return *_pcount;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)//一定要注意细节,注意不能自己赋值给自己
{
if (_ptr != sp._ptr)
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount = nullptr;
};
}