C++智能指针总结
1.为什么需要智能指针
我们在动态申请内存的时候,往往需要自己来管理和释放申请的内存,有的时候会因为疏忽或者未知原因而没有手动释放申请的资源,就会导致内存泄漏等问题。
例如:
int test(int a,int b)
{
int * p = (int*) malloc(sizeof(int)* 4);
if (b == 0)
{
throw "error";
}
free(p);
return b;
}
int main()
{
char * q = (char*)malloc(sizeof(char)* 5);
try
{
test(10, 0);
}
catch (const char* exception)
{
cout << "1";
}
catch (...)
{
cout << "2";
}
return 0;
}
这里指针q的资源忘记释放了,而且因为test函数中会抛出异常,所以函数内指针p的资源也没有释放,这些情况是比较容易出现的。
那么能否有个智能的东西替我们去管理这些资源呢,这时就需要智能指针了
2.内存泄漏
什么是内存泄漏:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
如何避免内存泄漏:
1、工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
2、 采用RAII思想或者智能指针来管理资源。
3、 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4、 出问题了使用内存泄漏工具检测。
3.智能指针的使用与原理
3.1RAII
RAII (Resource Acquisition Is Initialization) 是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
1、不需要显示的释放资源
2、采用这种方式,对象所需资源在其生命周期中始终有效
例如:
template<typename T>
class smartPtr{
public:
smartPtr(T * ptr = nullptr)
:_ptr(ptr)
{
}
~smartPtr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
T * _ptr;
};
void merge(int * a, int n)
{
int * temp = (int*)malloc(sizeof(int)*n);
smartPtr<int> sp (temp);
//可能会抛出异常的语句
vector<int>vec(100000000, 10);
}
int main()
{
try
{
int a[5] = { 1, 2, 3, 4, 5 };
merge(a,5);
}
catch (const exception & e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "unknown" << endl;
}
return 0;
}
这里只是实现了一个简陋的包含RAII概念的用法,还不能算是智能指针,还需重载operator* 和operator->等指针的用法,这里先不细究。
3.2 std::auto_ptr
在C++98版本的库中就提供了auto_ptr这种智能指针。要使用智能指针需要包含memory这个库,如下是使用auto_ptr的示例:
#include <iostream>
#include <memory>
using namespace std;
class Date
{
public:
Date() { cout << "Date()" << endl; }
~Date(){ cout << "~Date()" << endl; }
int _year;
int _month;
int _day;
};
int main()
{
auto_ptr<Date> ap(new Date);
auto_ptr<Date> copy(ap);
//auto_ptr的问题,当对象拷贝或者赋值后,前面的对象就被悬空了
//所以auto_ptr基本上已经不再使用
ap->_year = 2018;//通过悬空对象进行访问资源就会出问题
return 0;
}
3.2 std::unique_ptr
C++11开始提供一种更靠谱的智能指针unique_ptr,unique_ptr的设计思路非常的粗暴–防拷贝,也就是不让拷贝和赋值。
简单实现一个UniquePtr
template<class T>
class UniquePtr
{
UniquePtr(T* ptr = nullptr)
:_ptr(ptr)
{
}
~UniquePtr()
{
if (_ptr)
{
delete _ptr;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
// C++98防拷贝的方式:只声明不实现+声明成私有
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
//C++11防拷贝方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
T* _ptr;
};
3.3 std::shared_ptr
C++11开始提供更靠谱且支持拷贝的智能指针shared_ptr。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
1、 shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2、 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3、如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4、 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
4.C++11和boost中智能指针的关系
1、 C++ 98 中产生了第一个智能指针auto_ptr.
2、 C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
3、 C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
4、 C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
5.RAII扩展
RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
template<class Mutex>
class LockGuard
{
public:
LockGuard(Mutex& mtx)
:_mutex(mtx)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象
Mutex& _mutex;
};
mutex mtx;
static int n = 0;
void Func()
{
for (size_t i = 0; i < 1000000; ++i)
{
LockGuard<mutex> lock(mtx);
++n;
}
}
int main()
{
int begin = clock();
thread t1(Func);
thread t2(Func);
t1.join();
t2.join();
int end = clock();
cout << n << endl;
cout << "cost time:" << end - begin << endl;
return 0;
}