目的:实现堆内存的自动回收(垃圾回收机制)
智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。
智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。
补充:
C++ 智能指针底层是采用引用计数的方式实现的。
简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整型值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。
当堆空间对应的整型值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉(自动执行delete p)
RAII机制
RAII机制(资源和对象绑定, 局部对象自动销毁)
这种机制把资源的声明周期和对象的声明周期绑定, 存在的问题就是资源的当前使用者是唯一的, 出现赋值则情况需要特殊处理.
在类的构造函数中申请资源并使用,最后在析构函数中释放资源
智能指针是利用了一种叫做**RAII(资源获取即初始化)**的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
#include
std::shared_ptr p1; //不传入任何实参
std::shared_ptr p2(nullptr); //传入空指针 nullptr
std::shared_ptr p3(new int(10));//
std::shared_ptr p3 = std::make_shared(10);//这两种方式创建的p3完全相同
//调用拷贝构造函数
std::shared_ptr p4(p3);//或者 std::shared_ptr p4 = p3;
//调用移动构造函数
std::shared_ptr p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);
std::make_shared
模板函数,其可以用于初始化 shared_ptr 智能指针
std::move(p4)
,该函数会强制将 p4 转换成对应的右值,因此初始化 p5 调用的是移动构造函数
同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常
int* ptr = new int;
std::shared_ptr p1(ptr);
std::shared_ptr p2(ptr);//错误
不能将指针直接赋值给一个智能指针,一个是类,一个是指针。
例如
std::shared_ptr
的写法是错误的
get() | 获得 shared_ptr 对象内部包含的普通指针。 |
reset() | 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针; 当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 |
use_count() | 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 |
#include
#include
using namespace std;
int main()
{
//构建 2 个智能指针
std::shared_ptr p1(new int(10));
std::shared_ptr p2(p1);
//输出 p2 指向的数据
cout << *p2 << endl;
p1.reset();//引用计数减 1,p1为空指针
if (p1) {
cout << "p1 不为空" << endl;
}
else {
cout << "p1 为空" << endl;
}
//以上操作,并不会影响 p2
cout << *p2 << endl;
//判断当前和 p2 同指向的智能指针有多少个
cout << p2.use_count() << endl;
return 0;
}
运行结果
10
p1 为空
10
1
每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。
这也就意味着,每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。
std::unique_ptr p1();
std::unique_ptr p2(nullptr);
std::unique_ptr p3(new int);
std::unique_ptr p4(new int);
std::unique_ptr p5(p4);//错误,堆内存不共享,没有拷贝构造函数
std::unique_ptr p5(std::move(p4));//正确,调用移动构造函数
对于调用移动构造函数的 p4 和 p5 来说,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)。
⭐基于 unique_ptr 类型指针不共享各自拥有的堆内存,因此 C++11 标准中的 unique_ptr 模板类没有提供拷贝构造函数,只提供了移动构造函数
不能赋值,不能拷贝
//std::unique_ptr uptr2 = uptr; //不能賦值
//std::unique_ptr uptr2(uptr); //不能拷貝
release() | 释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁。 |
reset§ | 其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针; 反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后获取 p 所指堆内存的所有权(p 为 nullptr)。 |
基本操作
// 智能指针的创建
unique_ptr u_i; //创建空智能指针
u_i.reset(new int(3)); //绑定动态对象
unique_ptr u_i2(new int(4));//创建时指定动态对象
unique_ptr u(d); //创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 delete
// 所有权的变化
int *p_i = u_i2.release(); //释放所有权
unique_ptr u_s(new string("abc"));
unique_ptr u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针”
u_s2.reset(u_s.release()); //所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
#include
#include
using namespace std;
int main()
{
std::unique_ptr p5(new int);
*p5 = 10;
// p 接收 p5 释放的堆内存
int * p = p5.release();
cout << *p << endl;
//判断 p5 是否为空指针
if (p5) {
cout << "p5 is not nullptr" << endl;
}
else {
cout << "p5 is nullptr" << endl;
}
std::unique_ptr p6;
//p6 获取 p 的所有权
p6.reset(p);
cout << *p6 << endl;;
return 0;
}
运行结果
10
p5 is nullptr
10
该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用
借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
std::weak_ptr wp1;
std::weak_ptr wp2 (wp1);
//下面是常用法
std::shared_ptr sp (new int);
std::weak_ptr wp3 (sp);
reset() | 将当前 weak_ptr 指针置为空指针。 |
use_count() | 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。 |
expired() | 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。 |
lock() | 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针(若返回 nullptr ,则说明资源已经不存在,放弃对资源继续操作。);反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。 |
基本操作
weak_ptr w; //创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr w(sp); //与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p; //p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset(); //将 w 置空
w.use_count(); //返回与 w 共享对象的 shared_ptr 的数量
w.expired(); //若 w.use_count() 为 0,返回 true,否则返回 false
w.lock(); //如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr
#include
#include
using namespace std;
int main()
{
std::shared_ptr sp1(new int(10));
std::shared_ptr sp2(sp1);
std::weak_ptr 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;
}
运行结果
2
1
10
例子1
#include
#include
class Woman;
class Man {
private:
//std::weak_ptr _wife;
std::shared_ptr _wife;
public:
void setWife(std::shared_ptr woman) {
_wife = woman;
}
void doSomthing() {
if(_wife.lock()){}
}
~Man() {
std::cout << "kill man\n";
}
};
class Woman {
private:
//std::weak_ptr _husband;
std::shared_ptr _husband;
public:
void setHusband(std::shared_ptr man) {
_husband = man;
}
~Woman() {
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv) {
std::shared_ptr m(new Man());
std::shared_ptr w(new Woman());
if(m && w) {
m->setWife(w);
w->setHusband(m);
}
return 0;
}
➡weak_ptr 对象引用资源时不会增加引用计数,但是它能够通过 lock() 方法来判断它所管理的资源是否被释放。
做法就是上面的代码注释的地方取消注释,取消 Woman 类或者 Man 类的任意一个即可,也可同时取消注释,全部换成弱引用 weak_ptr。
既然 weak_ptr 不增加资源的引用计数,那么在使用 weak_ptr 对象的时候,资源被突然释放了怎么办呢?
通过 weak_ptr 来间接访问资源➡ lock() 成员函数
答案是在需要访问资源的时候 weak_ptr 为你生成一个shared_ptr,shared_ptr 能够保证在 shared_ptr 没有被释放之前,其所管理的资源是不会被释放的。
创建 shared_ptr 的方法就是 lock() 成员函数。
例子2
struct A;
struct B;
struct A {
std::shared_ptr pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
struct B {
std::shared_ptr pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
auto a = std::make_shared();
auto b = std::make_shared();
a->pointer = b;
b->pointer = a;
}
解决这个问题的办法就是使用弱引用指针 std::weak_ptr
,std::weak_ptr
是一种弱引用,不会引起引用计数增加。
在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。
参考:
http://c.biancheng.net/view/7898.html
http://c.biancheng.net/view/vip_8672.html
http://c.biancheng.net/view/vip_8673.html
https://www.cnblogs.com/wxquare/p/4759020.html
https://mp.weixin.qq.com/s/xDagaZOfEFFEQVnSxByhcQ
https://blog.csdn.net/lyly_h/article/details/108312904
https://www.kancloud.cn/machh03/cpp11/2081459
https://wizardmerlin.github.io/posts/b43344a7/
https://blog.csdn.net/k346k346/article/details/81478223
https://veifi.com/?p=57
https://changkun.de/modern-cpp/zh-cn/05-pointers/