C++中给一个指针用new新建了一个对象并分配内存空间后,需要在对象作用域结束之前用delete回收内存。而因为各种原因,delete可能会被漏掉,例如逻辑设计有误,导致delete之前先return了,或者delete之前有些操作抛出异常终止了,这样就会导致内存泄露。
智能指针最先引入的是auto_ptr,然后在C++11标准中删除了auto_ptr,而新加入了shared_ptr,unique_ptr和weak_ptr。智能指针最基本也是最重要的行为,就是在该删除的时候,自动删除,而不依赖程序员去调用。接下来分别介绍这四个只能指针。
auto_ptr
auto_ptr是一个封装后的类,它有一个显示的构造函数,接收一个指针。不能将一个指针用赋值运算符赋值给它,只能用构造方法。可以通过->
符号访问所指成员的成员变量和成员函数,通过*
来获得所指向的对象。
#include
#include
#include
using namespace std;
int main(void) {
auto_ptr ap(new string("hehe"));
cout << ap->length() << endl; // 输出:4
cout << *ap << endl; // 输出:hehe
return 0;
}
auto_ptr之所以被弃用,是因为对auto_ptr进行操作时,指针的所有权将被转移,从而原先的auto_ptr拥有空指针,而出错。例如下面:
#include
#include
#include
using namespace std;
int main(void) {
auto_ptr ap(new string("hehe"));
auto_ptr ap2;
ap2 = ap;
cout << *ap2 << endl; // 输出:hehe
cout << *ap << endl; // 出错,ap此时为空
return 0;
}
因此,在C++11标准中,auto_ptr被移除了,以三个新智能指针取而代之。
unique_ptr
unique_ptr和auto_ptr非常接近,但是它不允许赋值操作,也就不会发生所有权转移的问题。同时,unique_ptr相对更智能,对于返回的临时变量,如fun函数返回了一个unique_ptr,它可以接管其所有权。
#include
#include
#include
using namespace std;
unique_ptr fun()
{
return unique_ptr(new string("haha"));
}
int main(void) {
unique_ptr up(new string("hehe"));
unique_ptr up2;
// up2 = up; // 错误:不能赋值
up2 = move(up); // 正确:可以用move赋值,赋值后up为空
cout << *up2 << endl; // 输出:hehe
up2 = fun();
cout << *up2 << endl; // 输出:haha
return 0;
}
shared_ptr
shared_ptr和unique_ptr不同,它允许多个智能指针指向同一个对象,通过一个引用计数来统计仍在作用域内的智能指针数量,当数量为0即最后一个智能指针走出定义域的时候,它才会把删除内存空间。
#include
#include
#include
using namespace std;
class Node2;
class Node1 {
public:
~Node1() {
cout << "destructed" << endl;
}
shared_ptr next;
};
class Node2 {
public:
~Node2() {
cout << "destructed" << endl;
}
shared_ptr next;
};
int main(void) {
shared_ptr sp(new string("hehe"));
cout << sp.use_count() << endl; // 输出1
shared_ptr sp2 = sp;
cout << sp2.use_count() << endl; // 输出2
shared_ptr sp3 = sp;
cout << sp.use_count() << endl; // 输出3
cout << *sp << endl // 输出hehe
<< *sp2 << endl // 输出hehe
<< *sp3 << endl; // 输出hehe
sp3.reset(); // sp3被销毁,sp和sp2不影响
cout << *sp << endl // 输出hehe
<< *sp2 << endl // 输出hehe
<< sp2.use_count() << endl; // 输出2,一个已销毁
shared_ptr n1(new Node1());
shared_ptr n2(new Node2());
n1->next = n2; n2->next = n1;
cout << n1->next.use_count() << endl; // 输出2
// 最后没有输出 deleted
return 0;
}
上面的代码显示了shared_ptr的作用和引用计数。但shared_ptr有一个问题在于,如果两个类互相以shared_ptr指向对方,引用计数为2。跳出作用域时,两个资源的引用计数减1,仍为1,故无法销毁,在上面代码的最后,没有输出"destructed"字样,说明没有调用析构函数,即shared_ptr没有销毁,产生死循环。
weak_ptr
针对上面shared_ptr存在的循环引用的问题,只要把Node1或Node2中的任意一个shared_ptr改成weak_ptr就可以了。程序的最后会输出两个destructed字样,说明两个实例都得到了析构。
需要注意的是,weak_ptr不能直接访问资源,要用lock()方法得到一个shared_ptr才可以访问。
#include
#include
#include
using namespace std;
class Node2;
class Node1 {
public:
~Node1() {
cout << "destructed" << endl;
}
weak_ptr next;
};
class Node2 {
public:
~Node2() {
cout << "destructed" << endl;
}
shared_ptr next;
};
int main(void) {
shared_ptr sp(new string("hehe"));
cout << sp.use_count() << endl; // 输出1
shared_ptr sp2 = sp;
cout << sp2.use_count() << endl; // 输出2
shared_ptr sp3 = sp;
cout << sp.use_count() << endl; // 输出3
cout << *sp << endl // 输出hehe
<< *sp2 << endl // 输出hehe
<< *sp3 << endl; // 输出hehe
sp3.reset(); // sp3被销毁,sp和sp2不影响
cout << *sp << endl // 输出hehe
<< *sp2 << endl // 输出hehe
<< sp2.use_count() << endl; // 输出2,一个已销毁
shared_ptr n1(new Node1());
shared_ptr n2(new Node2());
n1->next = n2; n2->next = n1;
auto sp_w = n1->next.lock(); // 从weak_ptr获取shared_ptr
cout << n1->next.use_count() << endl; // 输出2
// 最后输出2次deleted
return 0;
}
智能指针的实现
智能指针首先是一个模板类,将原始指针封装在里面,并在析构函数中对原始指针进行判断和销毁。智能指针最大的难点在于实现引用计数。
下面的例子中,我们构造一个辅助模板类U_Ptr来操作原始指针,这个类包括了引用计数。SmartPtr模板类是U_Ptr模板类的友元,可以对它进行操作。初始化时,将U_Ptr的count值置为1,在拷贝构造函数和重载的赋值运算符中,对count进行增减操作。最后在析构函数中,根据count值判定是否执行真正的销毁操作。下面是一个较好的shared_ptr参考实现:
// 本实现代码来自https://www.cnblogs.com/QG-whz/p/4777312.html
// 模板类作为友元时要先有声明
template
class SmartPtr;
// 辅助类
template
class U_Ptr
{
private:
// 该类成员访问权限全部为private,因为不想让用户直接使用该类
// 定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
friend class SmartPtr;
// 构造函数的参数为基础对象的指针
U_Ptr(T *ptr) :p(ptr), count(1) {}
// 析构函数
~U_Ptr() { delete p; }
// 引用计数
int count;
// 基础对象指针
T *p;
};
// 智能指针类
template
class SmartPtr
{
public:
// 构造函数
SmartPtr(T *ptr) :rp(new U_Ptr(ptr)) {}
// 复制构造函数
SmartPtr(const SmartPtr &sp) :rp(sp.rp) { ++rp->count; }
// 重载赋值操作符
SmartPtr& operator=(const SmartPtr& rhs) {
++rhs.rp->count; // 首先将右操作数引用计数加1,
if (--rp->count == 0) // 然后将引用计数减1,理解为原有的对象-1了,可以应对自赋值
delete rp;
rp = rhs.rp;
return *this;
}
// 重载*操作符
T & operator *()
{
return *(rp->p);
}
// 重载->操作符
T* operator ->()
{
return rp->p;
}
// 析构函数
~SmartPtr() {
if (--rp->count == 0) // 当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
delete rp;
else
cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
private:
// 辅助类对象指针
U_Ptr *rp;
};
参考
C++ 引用计数技术及智能指针的简单实现