本章内容:
(1)shared_ptr、weak_ptr、unique_ptr的介绍
(2)单独使用share_ptr造成的内存泄漏
(3)shared_ptr和weak_ptr的配合使用
C/C++ 为了实现对内存的细粒度的操作,没有设计垃圾收集器。因此,使用 C/C++ 编写项目时,开发人员需要格外注意内存的申请和释放
。本文介绍了shared_ptr
和weak_ptr
的使用,分析了它们对内存泄漏的检测方式,并指出它们所存在的不足。希望读者可以通过改进指针算法或 share_ptr 来规避内存泄漏,尽量不要写出连检测算法也无法处理的代码结构。
share_ptr是C++11新添加的智能指针,它限定的资源可以被多个指针共享。
只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。
C++11 weak_ptr智能指针和 shared_ptr、unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的。weak_ptr( T 为指针所指数据的类型)定义在头文件,并位于 std 命名空间中。因此,要想使用 weak_ptr 类型指针,程序中应首先包含如下 2 条语句:
#include
using namespace std;
// 第 2 句并不是必须的,可以不添加,则后续在使用 unique_ptr 指针时,必须标注std::。
需要注意的是,C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
需要注意的是,当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。
除此之外,weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
创建一个 weak_ptr 指针,有以下 3 种方式:
std::weak_ptr<int> wp1;
std::weak_ptr<int> wp2 (wp1);
若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);
由此,wp3 指针和 sp 指针有相同的指针。再次强调,weak_ptr 类型指针不会导致堆内存空间的引用计数增加或减少。
和 shared_ptr、unique_ptr 相比,weak_ptr 模板类提供的成员方法不多
weak_ptr指针可调用的成员方法
operator=() | 重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。 |
---|---|
swap(x) | 其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。 |
reset() | 将当前 weak_ptr 指针置为空指针。 |
use_count() | 看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。 |
expired() | 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放) |
lock() | 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。 |
再次强调,weak_ptr 模板类没有重载 * 和 -> 运算符,因此 weak_ptr 类型指针只能访问某一 shared_ptr
指针指向的堆内存空间,无法对其进行修改。
#include
#include
using namespace std;
int main()
{
std::shared_ptr<int> sp1(new int(10));
std::shared_ptr<int> sp2(sp1);
std::weak_ptr<int> wp(sp2);
//输出和 wp 同指向的 shared_ptr 类型指针的数量
cout << wp.use_count() << endl;
//释放 sp2
sp2.reset();
cout << wp.use_count() << endl;
//借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
cout << *(wp.lock()) << endl;
return 0;
}
unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权。它取代了C++98中的auto_ptr。
unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
#include
#include
using namespace std;
struct Task {
int mId;
Task(int id) :mId(id) {
cout << "Task::Constructor" << endl;
}
~Task() {
cout << "Task::Destructor" << endl;
}
};
int main()
{
// 通过原始指针创建 unique_ptr 实例
//shared_ptr taskPtr(new Task(23));
//int id = taskPtr->mId;
//调用其lock函数来获得shared_ptr示例,进而访问原始对象。
//weak_ptr weak1 = taskPtr;
//int id = weak1.lock()->mId;
unique_ptr<Task> taskPtr(new Task(23));
int id = taskPtr->mId;
cout << id << endl;
return 0;
}
不管函数正常退出还是异常退出(由于某些异常),也会
始终调用taskPtr的析构函数
。因此,原始指针将始终被删除并防止内存泄漏。
两个shared_ptr指针互相引用导致的资源释放失败的例子:
代码如下(示例):
#include
#include
#include
using namespace std;
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout << "A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout << "B delete\n";
}
};
void fun() {
shared_ptr<B> pb(new B());
cout << "pb.use_count " << pb.use_count() << endl;//1
shared_ptr<A> pa(new A());
cout << "pa.use_count " << pa.use_count() << endl;//1
pb->pa_ = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->pb_ = pb;
//由于share_ptr是共享资源,所以pb所指向的资源的引用计数也会加1
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
}//程序结束时,没有调用A和B的析构函数
int main()
{
fun();
system("pause");
return 0;
}
注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:
A* p = new A(10);
shared_ptr sp1(p), sp2(p);
sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
代码如下(示例):
#include
#include
#include
using namespace std;
class B;
class A
{
public:
weak_ptr<B> pb_weak;
~A()
{
cout << "A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout << "B delete\n";
}
void print() {
cout << "This is B" << endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
cout << "pb.use_count " << pb.use_count() << endl;//1
shared_ptr<A> pa(new A());
cout << "pa.use_count " << pa.use_count() << endl;//1
pb->pa_ = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->pb_weak = pb;
cout << "pb.use_count " << pb.use_count() << endl;//1:弱引用不会增加所指资源的引用计数use_count()的值
cout << "pa.use_count " << pa.use_count() << endl;//2
shared_ptr<B> p = pa->pb_weak.lock();
p->print();//不能通过weak_ptr直接访问对象的方法,须先转化为shared_ptr
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
}//函数结束时,调用A和B的析构函数
//资源B的引用计数一直就只有1,当pb析构时,B的计数减一,变为0,B得到释放,
//B释放的同时也会使A的计数减一,同时pa自己析构时也会使资源A的计数减一,那么A的计数为0,A得到释放。
int main()
{
fun();
system("pause");
return 0;
}