系列文章目录
本章内容:
(1)shared_ptr、weak_ptr、unique_ptr的介绍
(2)单独使用share_ptr造成的内存泄漏
(3)shared_ptr和weak_ptr的配合使用
文章目录
- 系列文章目录
- 前言
- 一、shared_ptr、weak_ptr、unique_ptr的使用
- 1.1 shared_ptr
- 1.2 weak_ptr
- 1、weak_ptr指针的创建
- 2、weak_ptr模板类提供的成员方法
- 1.3 unique_ptr
- 二、使用步骤
- 1.shared_ptr单独使用案例
- 2.weak_ptr和shared_ptr配合使用案例
- 总结
前言
C/C++ 为了实现对内存的细粒度的操作,没有设计垃圾收集器。因此,使用 C/C++ 编写项目时,开发人员需要格外注意内存的申请和释放。本文介绍了shared_ptr和weak_ptr源码,分析了它们对内存泄漏的检测方式,并指出它们所存在的不足。希望读者可以通过改
进指针算法或 share_ptr 来规避内存泄漏,尽量不要写出连
检测算法也无法处理的代码结构。
提示:以下是本篇文章正文内容,下面案例可供参考
一、shared_ptr、weak_ptr、unique_ptr的使用
1.1 shared_ptr
share_ptr是C++11新添加的智能指针,它限定的资源可以被多个指针共享。
只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。
1.2 weak_ptr
C++11 weak_ptr智能指针
和 shared_ptr、unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的。weak_ptr( T 为指针所指数据的类型)定义在头文件,并位于 std 命名空间中。因此,要想使用 weak_ptr 类型指针,程序中应首先包含如下 2 条语句:
#include <memory>
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 类型指针只能访问所指的堆内存,而无法修改它。
1、weak_ptr指针的创建
创建一个 weak_ptr 指针,有以下 3 种方式:
- 可以创建一个空 weak_ptr 指针,例如:
std::weak_ptr<int> wp1;
- 凭借已有的 weak_ptr 指针,可以创建一个新的 weak_ptr 指针,例如:
std::weak_ptr<int> wp2 (wp1);
若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。
- weak_ptr 指针更常用于指向某一 shared_ptr 指针拥有的堆内存,因为在构建 weak_ptr 指针对象时,可以利用已有的 shared_ptr 指针为其初始化。例如:
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);
由此,wp3 指针和 sp 指针有相同的指针。再次强调,weak_ptr 类型指针不会导致堆内存空间的引用计数增加或减少。
2、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 <iostream>
#include <memory>
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;
}
1.3 unique_ptr
unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权。它取代了C++98中的auto_ptr。
unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
#include <iostream>
#include <memory>
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<Task> taskPtr(new Task(23));
//int id = taskPtr->mId;
//调用其lock函数来获得shared_ptr示例,进而访问原始对象。
//weak_ptr<Task> weak1 = taskPtr;
//int id = weak1.lock()->mId;
unique_ptr<Task> taskPtr(new Task(23));
int id = taskPtr->mId;
cout << id << endl;
return 0;
}
不管函数正常退出还是异常退出(由于某些异常),也会
始终调用taskPtr的析构函数
。因此,原始指针将始终被删除并防止内存泄漏。
二、使用步骤
1.shared_ptr单独使用案例
两个shared_ptr指针互相引用导致的资源释放失败的例子:
代码如下(示例):
#include <iostream>
#include <memory>
#include <string>
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 <A> sp1(p), sp2(p);
sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
2.weak_ptr和shared_ptr配合使用案例
代码如下(示例):
#include <iostream>
#include <memory>
#include <string>
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;
}
总结
- weak_ptr是一种用于解决shared_ptr相互引用时产生死锁问题的智能指针。如果有两个shared_ptr相互引用,那么这两个shared_ptr指针的引用计数永远不会下降为0,资源永远不会释放。weak_ptr是对对象的一种弱引用,它不会增加对象的use_count,weak_ptr和shared_ptr可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr也可以通过调用lock函数来获得shared_ptr。
- weak_ptr指针通常不单独使用,只能和 shared_ptr
类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。 - weak_ptr并没有重载operator->和operator
*操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。