四、智能指针
01 直接内存管理(new/delete)、创建新工程观察内存泄漏
1 直接内存管理(new/delete)
2 创建新工程,观察内存泄漏
直接内存管理(new/delete)
C语言中是
malloc/free
定义初值
int *pointi = new int(100);
string *points = new string(5, 'a'); //"aaaaa"
vector<int> *pointv = new vector<int>{1, 2, 3, 4, 5};
概念 “值初始化”:用()
来初始化
int *pointi = new int();
auto pointi_t = new auto(pointi);
//前面的auto推断出的是int **
推荐在delete
后将该指针设置成nullptr
C++中出现了智能指针,会帮助程序猿收回内存。
创建新工程,观察内存泄漏
MFC应用程序能够在一定程度上(程序推出的时候),帮助发现内存泄漏。
MFC:微软公司出的基础的程序框架(MFC基础类库),可以生成一个带窗口(带界面)的程序框架,MFC简化了很多界面开发工作。
快捷键 ctrl + ]
跳转到对应的{
或 }
02 new、delete探秘,智能指针概述、shared_ptr基础
1 new/delete探秘
new/delete是什么
operator new()和operator delete()
基本new如何记录分配的内存大小供delete使用
申请和释放一个数组
为什么new/delete、new[]/delete[]要配套使用2 智能指针总述
3shared_ptr
基础常规初始化(
shared_ptr
和new
配合)
make_shared
函数
new/delete探秘
new/delete是什么?
new/delete
类对象时,有初始化/释放的能力(调用构造函数/析构函数),这些是malloc/free
所不能的。
operator new()和operator delete()
new/delete是操作符,operater new()和operator delete()是函数
new
简单理解为做了两件事:a)分配内存(实际上通过operator new()
);b)给内存初始化delete
也做了两件事:a)反初始化(调用析构函数);b)释放内存(operator delete()
)
基本new如何记录分配的内存大小供delete使用
不同编译器new内部有不同的实现方式,new内部有记录机制,记录分配出多少内存。
申请和释放一个数组
int *p = new int[2];
delete[] p;
如果申请一个类数组,会在内存中存入该数组中类的个数,以便与调用等量的析构函数。
智能指针总述
- 裸指针:直接用
new
返回的指针,强大灵活,但是需要开发者全程维护。 - 智能指针:理解为“裸指针”进行了包装,能够“自动释放所指向的对象内存”。C++标准库std提供了四种智能指针:
- 都是类模版
auto_ptr
(C++98)已经被unique_ptr取代unique_ptr
(C++11):独占式指针。同一时间内,只有一个指针指向该对象shared_ptr
(C++11):共享式指针。多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被销毁weak_ptr
(C++11)用来辅助shared_ptr
- 都是类模版
- 用来帮助我们进行动态分配对象(new出来的对象)的生命周期的管理。能够有效防止内存泄漏
shared_ptr基础
常规初始化(shared_ptr和new配合)
每个share_ptr的拷贝都指向相同的内存
工作原理:引用计数。只有最后一个指向该内存的shared_ptr
不需要再指向该对象时,才会析构这个对象。计数的作用是每增加一个shared_ptr
count + 1,销毁指针到count为0时,就会释放内存。
std::shared_ptr<int> pi(new int(100));
//智能指针explicit不允许隐式转换
//故std::shared_ptr<int> pi = new int;是错误的
make_share函数:标准库里的函数模版
在自定义删除器时会受到限制
shared_ptr<int> pt = make_shared<int>(100);
03 shared_ptr常用操作、计数、自定义删除器
1 shared_ptr引用计数的增加和减少
引用计数的增加
引用计数的减少2 shared_ptr指针常用操作
use_count()
unique()
reset()
*解应用
get()
swap()
=nullptr
智能指针名字作为判断条件
指定删除器以及数组问题
shared_ptr引用计数的增加和减少
每个shared_ptr
都会记录有多少个其他的shared_ptr
指向相同的对象。
shared_ptr指针常用操作
use_count()
:返回多少个智能指针指向某个对象,主要用于调试目的unique()
:是否该智能指针独占某个指向的对象,如果是,返回Truereset()
:复位/重置- 不带参数时
- 若该指针是唯一指向该对象的指针,那么释放该对象,并将该指针置空(nullptr)
- 若该指针不是唯一指向该对象的指针,那么不释放该对象,但计数减一,该指针置空(nullptr)
- 带参数时(一般是new出来的指针)
- 若该指针是唯一指向该对象的指针,那么释放该对象,并将该指针指向新对象
- 若该指针不是唯一指向该对象的指针,那么不释放该对象,但计数减一,该指针指向新对象
- 不带参数时
*解应用
:获得指针指向的对象get()
:返回保存的指针(裸指针)。用于有些函数的参数需要的是一个内置裸指针而不是智能指针swap()
:交换两个智能指针所指向的对象=nullptr
- 将所指向的对象引用计数减一,若引用计数变为0,则释放该指针指向的对象
- 将智能指针置空
- 指定删除器以及数组问题
- 可以指定自己的删除器函数取代系统的默认删除器
- 管理动态数组时
//定义一个删除器函数
void deletefunc(int *p)
{
delete []p;
}
shared_ptr<int> pt(new int[100], deletefunc);
//打包
template<typename T>
shared_ptr<T> make_array(size_t n)
{
return shared_ptr<T>(new T[n], default_delete<T[]>());
}
shared_ptr pt = make_array<int>(3); //长度为3的整形数组,自定义删除器
//lambda函数做自定义删除器
shared_ptr<int> pt(new int[10], [](int *p) {
delete []p;
}
//default_delete做删除器
shared_ptr<int> pt(new int[10], default_delete<int[]>());
//C++17
shared_ptr<int[]> pt(new int[10]); //在<>中加[]
size_t
源码中 -> typedef unsigned __int64 size_t;
- 32位系统的size_t是4字节的unsigned int
- 64位系统的size_t是8字节的unsigned long long
weak_ptr
概述、weak_ptr
常用操作、尺寸
1 weak_ptr概述
weak_ptr的创建
2 weak_ptr常用操作
use_count()
expired()
reset()
lock()3 尺寸问题
weak_ptr概述
weak_ptr
:用来辅助shared_ptr
进行工作。这个智能指针指向一个由shared_ptr
管理的对象。但是它并不控制所指向对象的生命周期(不会改变shared_ptr
的引用计数)。shared_ptr
能够照常释放所指向的对象。
weak_ptr的创建
一般都用shared_ptr
初始化。
auto pi = make_shared<int>(100);
weak_ptr<int> pwi(pi);
//不会改变强引用(strong ref)的引用计数,但是会增加弱引用(weak ref)计数
weak_ptr常用操作
use_count
:获取当前所观测资源的强引用计数expired()
:是否过期,用来判断所观测资源是否被释放reset()
:将该弱引用指针设置为lock()
:功能是检查weak_ptr
所指向的对象是否存在。如果存在返回一个指向该对象的shared_ptr
,如果不存在则返回一个空的shared_ptr
//接着
auto pi2 = pwi.lock(); //会使强引用加1,pwi仍是弱引用
尺寸问题
weak_ptr
的尺寸和shared_ptr
一样大,是裸指针的2倍(8字节)。实际上在它们内部包含了两个指针。
05 shared_ptr使用场景、陷阱、性能分析、使用建议
1
std::shared_ptr
使用场景
2std::shared_ptr
使用陷阱分析
3 性能说明尺寸问题
移动语义
std::shared_ptr
使用陷阱分析
- 慎用裸指针
- 当把一个普通裸指针绑定到了一个shared_ptr上之后,内存管理的责任就交给了智能指针,这时就不应该再用裸指针访问shared_ptr指向的内存了
- 不能用裸指针初始化多个shared_ptr,会导致多个智能指针之间不关联,删除器会由于两次释放相同内存而报错
- 慎用get()返回的指针
- get返回的指针不能delete,否则会异常
- 不能将其他智能指针绑到get返回的指针上
- 不要把类对象指针(this)作为shared_ptr返回,改用
enable_shared_from_this
(工作中需要返回该调用函数本身的类的智能指针时使用)- 工作原理:enable_shared_from_this中又一个弱指针weak_ptr,这个弱指针能够监视this,调用
shared_from_this
时,实际上调用了这个weak_ptr的lock方法
- 工作原理:enable_shared_from_this中又一个弱指针weak_ptr,这个弱指针能够监视this,调用
class CT : public enable_shared_from_this<CT>
{
public:
shared_ptr<CT> getself()
{
return shared_from_this(); //这个是enable_shared_from_this类模板中的方法
}
};
性能说明
尺寸问题
shared_ptr
和weak_ptr
均占8个字节,也就是两个指针,一个指针指向该指向对象的智能指针;一个指针指向控制块(包括强引用计数、弱引用计数、删除器、分配器等)
移动语义
移动构造一个新的智能指针
shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1));
会将p1指向的对象移动给p2,p1不再指向该对象(变成空指针)。
06 unique_ptr概述、常用操作
1 unique_ptr概述
常规初始化
make_unique函数2 unique_ptr常用操作
unique_ptr概述
独占式的概念(专属所有权):同一个时刻只能有一个unique_ptr指向一个对象。当被销毁时,所指向的对象同时也被销毁。
unique_ptr<int> pt(new int(103));
//c++14:make_unique
unique_ptr<int> pt = make_unique<int>(104);
auto pt = make_unique<int>(105);
unique_ptr常用操作
- 移动语义
unique_ptr<string> ps1(new string("ingenious"));
unique_ptr<string> ps2(std::move(ps1));
release()
:放弃对指针的控制权,切断指针和对象之间的联系。返回裸指针,并将该智能指针置空,该裸指针需手动释放
unique_ptr<string> ps1(new string("ingenuous"));
unique_ptr<string> ps2(ps1.release());
reset()
- 不带参数:释放智能指针指向的对象,并把指针置空
- 带参数:释放智能指针指向的对象,并将智能指针指向括号内的新对象
unique_ptr<string> ps3(new string("fractious"));
ps3.reset(ps1.release());
= nullptr
:释放指向的对象并置空该智能指针get()
:返回智能指针中保存的裸指针swap()
:交换两个指针所指向的对象- 转换成
shared_ptr
形式:如果unique_ptr为右值就可以转换成shared_ptr形式
auto func()
{
return unique_ptr<string> st(new string("fd")); //返回的是临时对象,是右值
}
shared_ptr<string> p = func();
07 返回unique_ptr、删除器、尺寸、智能指针总结
1 返回unique_ptr
2 指定删除器
3 尺寸问题
4 智能指针总结
指定删除器
格式: unique_ptr<指向的对象类型, 删除器类型> 指针名
void mydelete(string* p)
{
delete p;
p = nullptr;
}
//typedef void (*pr) (string *);
//using pr = void (*) (string *);
typedef decltype(mydelete)* pr;
unique_ptr<string, pr> point(new string("hei"), mydelete);
unique_ptr自定义删除器需要在模板参数中加入删除器函数的类型,例中提供了用函数指针做模板参数的情况。
尺寸问题
通常情况下,unique_ptr和裸指针的大小一样(4字节)。但是自定义删除器就可能增加所占用的内存。
总结
- 智能指针的设计目的:帮助释放内存,以防内存泄漏
- auto_ptr为什么被废弃?
- 不能再容器中保存
- 不能从函数中返回auto_ptr
- C++11提出的unique_ptr相比C++98的auto_ptr更安全
- … …