C++的内存管理一直是c++程序员的一个痛点,c++98中提供了auto_ptr智能指针用于管理堆对象,但其支持的操作有限(不支持多个指针指向同一个对象,不支持管理堆数组),限制了其应用。c++11标准中引入了boost库的shared_ptr和unique_ptr智能指针用于内存管理,大大方便了动态对象的管理。本文谈谈shared_ptr和unique_ptr的使用。
智能指针的构造函数被声明为explicit,所以,我们不能将一个内置指针隐式转换为一个智能指针,其支持的赋值方式如下:
{
std::shared_ptr
支持的操作:
p.get() 返回p中保存的指针
p.swap(q) 交换p和q中的指针
p.use_count 返回与p共享对象的智能指针数量
p.unique() 若p.use_count 为1,返回true,否则返回false
p=q 赋值操作,要求p和q都是shared_ptr,且所指向的对象能互相转换。此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为0,则释放其指向的对象,注意:这里不支持原生指针到智能指针的隐式转换。
另外,和一般指针一样,智能指针支持 *, -> 和 . 运算
可以使用reset函数来将一个新的指针赋予一个shared_ptr,与赋值类似,reset操作会更新引用计数。此操作经常与unique一起使用,来控制多个shared_ptr共享的对象,例子如下:
if(!p.unique()) //改变底层对象前,检查自己是否是当前对象仅有的用户
p.reset(new string(*p)); //非唯一用户,在改变对象之前分配新的拷贝
*p += newVal; //此时已经是唯一用户
shared_ptr用法2:
shared_ptr的缺省行为是当引用计数为0时删除其所指对象,但可以显示指定一个“删除动作”来改变这一行为,这可以通过创建shared_ptr有一种形式为:shared_ptr p(q, d) 来实现,其中d是一个函数或函数对象(或lamda表达式),可以使在释放对象时所完成的动作由delete变成d所指定的动作,具体的场景如互斥量释放,关闭文件句柄等。如:
pthread_mutex_t *pMutex = new pthread_mutex_();
pthread_mutex_init(&pMutex,NULL);
{
pthread_mutex_lock(pMutex);
//do something
...
}
使用智能指针形式:
pthread_mutex_t *pMutex = new pthread_mutex_();
pthread_mutex_init(&pMutex,NULL);
{
shared_ptr mutexPtr(pMutex, pthread_mutex_unlock);
pthread_mutex_lock(mutexPtr.get());
//do something
...
} //will auto unlock
进一步封装成类使用:
class Lock{
public:
explicit Lock(Mutex*pm): mutexPtr(pm, unlock) //使用unlock代替delete
{
lock(mutextPtr.get());
}
private:
std::shared_ptr mutexPtr; //使用shared_ptr代替raw pointer
};
tips:
1、 永远不要用get初始化另一个智能指针或者为另一个智能指针赋值,使用get获取内置指针应注意避免空间重复释放问题
如以下用法会导致悬空指针:
shared_ptr p(new int(42));
int *q = p.get();
{
shared_ptr(q);
}
int foo = *p; //p指向的内存已经被释放
2、 不要混用原生指针和智能指针
int *x(new int(42));
process(shared_ptr(x));
int j = *x;
其中,process定义如下:
void process(shared_ptr
顾名思义,unique_ptr指向一个给定的对象,具有唯一性,即其的引用计数为1,其也是一种智能指针。但与shared_ptr不同,其赋值方式不支持拷贝赋值(即拷贝构造函数的参数为unique_ptr类型),也不支持”=”赋值,其正确的赋值方式如下:
unique_ptr
或
unique_ptr
虽然不能通过拷贝或赋值unique_ptr,但可以通过unique_ptr的release或reset操作过度指针所有权(非const)。这两个操作定义如下:
u.release() u放弃对指针的控制权,返回原生指针,并将u置空
u.reset(q) 如果q不为nullptr,则令u指向这个对象;否则将u置为空
u.reset() 释放u指向的对象
应该注意release操作返回了原生指针,但是却不会释放其指向的内存,因此以下语句是错误的:
p2.release(); //p2不会释放内存,造成内存泄漏
unique_ptr的简单使用示例如下:
unique_ptr p1(new string("p1"));
cout << "*p1 = " << *p1 << endl;
unique_ptr p2(p1.release()); //所有权从p1过渡给p2
cout << "*p2 = " << *p2 << endl;
unique_ptr p3(new string("p3"));
p2.reset(p3.release()); //所有权从p3过渡给p2
cout << "After reset, *p2 = " << *p2 << endl;
输出结果:
*p1= p1
*p2= p1
After reset, *p2 =p3
不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr,最常见的是从函数返回一个unique_ptr:
unique_ptr clone(int p)
{
return unique_ptrret(new int(p));
}
p.s:
支持u = nullptr; 将指针置空;
C++98标准库中包含了一个auto_ptr类,其具有unique_ptr的部分特性,可以理解为unique_ptr是auto_ptr的升级版。auto_ptr不支持在容器中保存,也不能从函数中返回auto_ptr。
weak_ptr指向一个shared_ptr管理的对象,将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数,也不控制所指对象生存期,即“weak”的特点。其通过下面方式进行初始化:
Weak_ptr
w = sp sp可以是shared_ptr或weak_ptr,赋值后w共享p指向的对象
w.reset() 将w置空,但不影响对象的生存期
w.use_count() 即shared_ptr对应的use_count
w.expired() 若w.use_count为0,则为true;否则为false
w.lock() 如果expired为true,返回一个空的shared_ptr;否则返回一个指向w的对象的shared_ptr
其使用如下:
auto p =make_shared(42);
weak_ptrwp(p); //wp为弱共享,p的use_count不变
由于weak_ptr指向的对象可能不存在,所以使用时必须调用lock检查其指向的对象是否仍存在。
C++11标准库提供了一个可以管理new分配的数组的unique_ptr版本。使用方式如下:
unique_ptrspArray(new Object[2]);
类型说明符中的方括号
由于spArray指向一个数组,所以我们不能直接使用”.”和”->”成员运算符。这时,我们可以使用下标运算符[]来访问数组中的元素:
u[i] 返回u在数组中位置i处的对象,其中u指向一个数组
与unique_ptr不同,shared_ptr不支持直接管理动态数组,也不支持[]运算符。如果希望使用shared_ptr来管理动态数组,可以通过指定delete []作为删除器(不指定的话默认使用delete作为删除器):
shared_ptrsp(newObject[2], [](int *p){delete[] p;});
由于不支持下标运算符[],因此,为了访问元素,必须用get获取一个内置指针,然后通过它来访问数组元素,如下所示:
for(size_ti = 0; i < 2; ++i)
{
*(sp.get() + i) = i;
}