C++动态内存管理和智能指针:malloc和new,free和delete,在C语言中,我们通常用malloc和free来动态的管理内存,其中malloc用来在堆上开辟空间,而free用来释放malloc或其他在堆上动态开辟内存的函数所开辟的空间。在C++中,我们用new/delete;new[]/delete[]来动态的管理内存,相比于C语言中的malloc和free,他们之间有什么差别呢?
首先,它们的调用方式不相同,其中malloc和free采用下面的方式来调用
int* p=(int*)malloc(sizeof(int)*4);
//使用malloc函数开辟4个int类型的空间
free(p);
p=NULL;
//释放上面malloc所开辟的空间
在C++中,new和new[]/delete[]和delete主要采用下面的调用方式:
int* p=new int(4); //开辟一个int类型的对象,初值为4
delete p; //释放上面所开辟的空间
int* p1=new int[4]; //开辟4个int类型的空间,无初值
delete[] p1; //释放上面所开辟的空间
另外,它们之间更重要的差别是,new和delete,new[]和delete[]才开辟空间后和释放空间前还分别调用了所开辟类型的构造函数以及析构函数。如下图所示:
另外,C中的malloc开辟空间时如果出现了内存耗尽的情况的话会直接返回NULL,而C++中的new会抛出异常,不过这里我们也可以声明其不抛出异常使其在内存耗尽的情况下返回NULL.
int* p=new int[100000000000]; //出现内存耗尽,抛出异常的方式
int* p1=new(nothrow) int[10000000]; //不抛出异常,出现内存耗尽返回NULL
为什么需要智能指针
在平时写代码时,我们用new开辟内存空间时,即使我们记住了需要配对使用,但难免有时候会出现内存泄漏的情况;例如:
{
int* p=new int(5);
...
...
thorw ...;
` ...
delete p;
}
在上面的这段代码中,即使我们在最后对new分配的空间进行了delete,如果中间因为某些原因我们抛出了一个异常,并且我们在异常处理函数中没有对这块内存进行处理,那么这块内存便会一直停留在操作系统中,从而导致了内存泄漏。
对于这种情况,我们因此引入了智能指针,智能指针采用RAII(资源分配即初始化机制)的内存管理方式,即智能指针不是常规指针,只是他的行为类似指针,其实是一种管理内存的对象,在其构造时获取资源,在其对象生命控制期对其访问使用有效,最后在其析构期释放内存。对此不管是否抛出异常,只要离开了当前的作用域,作用域中的智能指针便会自动调用delete或delete[]进行释放资源。
在boost库中主要有下面几种智能指针:
auto_ptr
auto_ptr采用可以采用copy语义来转移指针资源的所有权的同时将原指针置为NULL,这跟通常理解的copy行为是不一致的,而这样的行为要有些场合下不是我们希望看到的,例如sort的快排实现中有将元素复制到某个局部临时对象中,但对于auto_ptr,却将原元素置为null,这就导致最后的排序结果中可能有大量的null从而导致异想不到的后果。
scoped_ptr
scoped_ptr和auto_ptr都表示唯一的所有权持有者,区别在于scoped_ptr不允许拷贝构造和赋值的发生,它这这些函数定义为私有成员函数,从而使外部对象无法进行访问。
下面我们来实现一个简单的scoped_ptr
template
class Scoped_ptr
{
public:
explicit Scoped_ptr(T* ptr=NULL)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
//return &(operatpr*()); //写法1
return _ptr;
}
~Scoped_ptr()
{
delete _ptr;
}
private:
Scoped_ptr(const Scoped_ptr& sp)
{}
operator=(const Scoped_ptr& sp)
{}
protected:
T* _ptr;
};
其中,编译器对->运算符的重载进行了优化
例如:
struct A
{
int _a;
int _b;
int _c;
};
Scoped_ptr pa(new A());
pa->_a;
//(pa->_a):pa.operator->->_a; 两步变一步
shared_ptr采用了引用计数的方式来管理内存,如下图:
使用库中的shared_ptr指针:
* shared_ptr不允许隐式的强制类型转换(构造函数和拷贝构造函数前面声明了explicit)
例如:不能这么使用shared_ptr
shared_ptr
pa=new int(1); //正确使用方式如下 shared_ptr
pa(new int(1));
返回值必须要显示的绑定到shared_ptr的指针上
例如:
shared_ptr
ReturnPtr() { //正确方式 shared_ptr
tmp(new int(1)); return tmp; //错误方式 return new int(1); }
p.reset() //若p的shared_ptr的引用计数为1,reset会释放p所指向的对象 make_shared //创建一个shared_ptr的对象 使用shared_ptr的误区:使用内置指针来访问shared_ptr和使用shared_ptr来管理内置指针 智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象(注意在使用get时不能delete指针,否则后面可能会发生对空指针的解引用的行为)
此时weak_ptr就出现了,它采用弱引用的方式,弥补了shared_ptr循环引用的问题它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
weak_ptr:
namespace boost
{
template
class weak_ptr { public: template
weak_ptr(const shared_ptr
& r); weak_ptr(const weak_ptr& r); ~weak_ptr(); T* get() const; bool expired() const; shared_ptr
lock() const; }; }
总结::智能指针能解决C++中的内存泄漏问题,我们应在程序中多去使用智能指针,另外还要注意shared_ptr引起的循环引用的问题。