知识点#
16.1 直接内存管理(new/delete)、创建新工程与观察内存泄漏#
- @ new与delete图
- ![[白板/P48 new与delete.canvas|P48 new与delete]]
16.1.1 直接内存管理(new/delete)#
[!tip] 动态内存分配
- @ 动态分配: 由程序员自己用
new为对象分配内存(new返回该对象的指针),用delete释放内存new出来的对象在==堆==上分配内存,必须自己delete释放,否则内存泄漏- 局部变量在==栈==上分配,离开作用域自动释放
- 静态变量(
static)在==静态内存==上,程序结束时释放
[!tip] new 初始化方式
- % 直接初始化:
int *p = new int(100);— 值为 100- % 值初始化:
int *p = new int();— 值被初始化为 0- % 默认初始化:
int *p = new int;— 值未定义(不确定)- % 类类型:
new会调用构造函数初始化,值初始化对自定义类无额外意义- %
const对象:const int *pci = new const int(200);
// new 与 auto 配合
string *mystr2 = new string(5, 'a');
auto mystr3 = new auto(mystr2); // mystr3 被推断为 string** 类型
// vector 初始化
vector<int> *pointv = new vector<int>{1, 2, 3, 4, 5};
[!tip] new/delete 使用注意事项
- !
new和delete必须==成对使用==,有new必须有delete- ! 同一块内存只能
delete==一次==,多次delete会报异常- !
delete后的内存==不能再使用==,否则运行异常- !
delete后建议将指针设为nullptr,避免悬空指针- ! 两个指针指向同一块内存时,
delete一个后另一个也不能再操作const对象可以被delete
16.1.2 创建新工程与观察内存泄漏#
[!tip] MFC 应用检测内存泄漏
- @ MFC 应用: 微软基础类库框架,程序退出时能报告内存泄漏
- 在
OnInitDialog函数中写入测试代码- 用
F5调试运行,退出后在"输出"窗口查看内存泄漏信息- “Detected memory leaks!” 表示出现内存泄漏,会给出泄漏字节数
16.2 new/delete 探秘、智能指针总述与 shared_ptr 基础#
16.2.1 new/delete 探秘#
[!tip] new/delete 本质
- @
new/delete: 都是==关键字(运算符)==,不是函数new做两件事: ① 分配内存 ② 调用构造函数初始化delete做两件事: ① 调用析构函数 ② 释放内存new通过调用operator new()分配内存delete通过调用operator delete()释放内存
[!tip] new/delete 与 malloc/free 的区别
- ! 最明显区别:
new/delete会==调用构造/析构函数==,malloc/free不会new相比malloc不但分配内存,还做初始化delete相比free不但释放内存,还做清理工作
[!tip] 数组的 new[]/delete[]
- !
new[]和delete[]必须==配套使用==new[]分配类对象数组时,会==多分配 4 字节==保存数组大小,供delete[]知道调用几次析构函数- 内置类型(如
int)new[]不会多分配 4 字节- ! 有自定义析构函数的类,用
delete(而非delete[])释放数组会==报异常==- 无自定义析构函数的类或内置类型,
delete和delete[]效果一样(但不推荐混用)
// 正确用法
int *p = new int[2];
delete[] p; // 规范写法
A *pA = new A[2]();
delete[] pA; // 必须用 delete[],调用 2 次析构函数
// 错误用法
A *pA = new A[2]();
delete pA; // 异常!只调用 1 次析构,且释放地址错乱
16.2.2 智能指针总述#
[!tip] 智能指针概念
- @ 智能指针: 对==裸指针==进行包装,能够==自动释放所指向的对象==
- 智能指针让程序员不用再担心
delete,程序更健壮- 优先选择智能指针,裸指针能做的智能指针都能做
[!tip] 四种智能指针
- %
std::shared_ptr: 共享式指针,多个指针指向同一对象,最后一个指针销毁时释放对象- %
std::unique_ptr: 独占式指针,同一时刻只有一个指针指向该对象(所有权可移交)- %
std::weak_ptr: 辅助shared_ptr工作,不控制对象生存期- %
std::auto_ptr: C++98 已有,已被unique_ptr取代,==不要再使用==
16.2.3 shared_ptr 基础#
[!tip] shared_ptr 初始化
- @
shared_ptr: 共享式智能指针,采用==引用计数==管理对象生存期- 多个
shared_ptr指向同一对象,最后一个不需要时才释放shared_ptr是explicit的,==不能隐式转换==
// 常规初始化
shared_ptr<int> pi(new int(100)); // 正确:直接初始化
// shared_ptr<int> pi2 = new int(100); // 错误:不能隐式转换
// make_shared(推荐)
shared_ptr<int> p2 = make_shared<int>(100); // 指向值为 100 的 int
shared_ptr<string> p3 = make_shared<string>(5,'a'); // 5 个 'a'
auto p5 = make_shared<string>(5, 'a'); // auto 简写
// 裸指针初始化(不推荐)
int *pi = new int;
shared_ptr<int> p1(pi); // 不推荐
shared_ptr<int> p1(new int); // 稍好,直接传递 new
[!tip] make_shared 优势
- @
make_shared: 标准库函数模板,==最安全和高效==的分配方式- 在堆中分配并初始化对象,返回
shared_ptr- 编译器只分配==一次内存==(对象+控制块),而
shared_ptr<T>(new T)至少分配两次- ! 使用
make_shared则==无法自定义删除器==
16.3 shared_ptr 常用操作、计数与自定义删除器等#
- @ shared_ptr图
- ![[白板/P49 shared_ptr.canvas|P49 shared_ptr]]
16.3.1 shared_ptr 引用计数的增加和减少#
[!tip] 引用计数增加的情况
- 1 用一个
shared_ptr初始化另一个shared_ptr- 1 将
shared_ptr作为实参传递给函数(值传递)- 1 函数返回
shared_ptr(有变量接收时)
[!tip] 引用计数减少的情况
- 1 给
shared_ptr赋新值,指向新对象- 1 局部
shared_ptr离开其作用域- 1 当引用计数变为 ==0== 时,自动释放所管理的对象
16.3.2 shared_ptr 指针常用操作#
[!tip] 常用成员函数
函数 说明 use_count()返回指向同一对象的智能指针数量(主要用于调试) unique()是否独占指向的对象(引用计数为 1 返回 true)reset()不带参:释放对象并置空;带参:指向新对象 get()返回保存的裸指针(小心使用,不要 delete)swap()交换两个智能指针所指向的对象 *p解引用,获得指向的对象 = nullptr引用计数减 1,若为 0 则释放对象,指针置空
// reset 用法
shared_ptr<int> pi(new int(100));
pi.reset(); // 释放对象,pi 置空
pi.reset(new int(1)); // 指向新内存
// get 用法
shared_ptr<int> myp(new int(100));
int *p = myp.get(); // 获取裸指针
// delete p; // 绝对不可以!
// 智能指针作为判断条件
if (ps1) { /* ps1 指向一个对象 */ }
[!tip] 自定义删除器
[!tip] 删除器与数组
- @ 删除器: 默认用
delete释放对象,可自定义删除逻辑shared_ptr指定删除器:在参数中添加删除器函数名或 lambda- !
shared_ptr管理动态数组时,默认删除器==不支持数组==,需自定义或用T[]
// 自定义删除器(函数)
void myDeleter(int *p) { delete p; }
shared_ptr<int> p(new int(12345), myDeleter);
// 自定义删除器(lambda)
shared_ptr<int> p2(new int(12345), [](int *p) { delete p; });
// 管理数组方式1:自定义删除器
shared_ptr<A> pA(new A[10], [](A *p) { delete[] p; });
// 管理数组方式2:尖括号加 [](推荐)
shared_ptr<A[]> pA(new A[10]); // 推荐!可直接用 pA[0], pA[1] 等
// 管理数组方式3:default_delete
shared_ptr<A> pA(new A[10], std::default_delete<A[]>());
[!tip] 删除器与类型
- ! 两个
shared_ptr删除器不同,只要指向类型相同,就属于==同一类型==- 这意味着可以放入同一容器中
make_shared无法自定义删除器
16.4 weak_ptr 简介、weak_ptr 常用操作与尺寸问题#
- @ weak_ptr图
- ![[白板/P50 weak_ptr.canvas|P50 weak_ptr]]
16.4.1 weak_ptr 简介#
[!tip] weak_ptr 概念
- @
weak_ptr: 弱引用智能指针,指向shared_ptr管理的对象,但==不控制对象生存期==- 绑定到
shared_ptr不会改变引用计数(弱引用不影响强引用计数)weak_ptr构造和析构不会增加/减少所指向对象的引用计数- 不能直接访问对象,必须通过
lock()获取shared_ptr
auto pi = make_shared<int>(100);
weak_ptr<int> piw(pi); // piw 弱共享 pi,强引用计数不变
// *piw; // 错误!不能直接访问
auto pi2 = piw.lock(); // 返回 shared_ptr,强引用计数 +1
if (pi2 != nullptr)
cout << "所指对象存在" << endl;
16.4.2 weak_ptr 常用操作#
[!tip] weak_ptr 常用成员函数
函数 说明 use_count()获取与该弱指针共享对象的 shared_ptr数量(强引用计数)expired()是否过期( use_count()为 0 返回true)reset()将弱引用指针置空,弱引用计数减 1 lock()检查对象是否存在,存在返回 shared_ptr,不存在返回空
auto pi = make_shared<int>(100);
weak_ptr<int> piw(pi);
int isc = piw.use_count(); // 1
pi.reset();
if (piw.expired()) // true,对象已释放
cout << "piw 已过期" << endl;
auto p2 = piw.lock(); // 返回空 shared_ptr
16.4.3 尺寸问题#
[!tip] 智能指针尺寸
shared_ptr和weak_ptr尺寸都是裸指针的 ==2 倍==(x86 下为 8 字节)- 包含两个裸指针:
- 1 指向所管理对象的指针
- 1 指向==控制块==的指针
- 控制块包含:强引用计数、弱引用计数、自定义删除器指针等
- 控制块由第一个指向某对象的
shared_ptr创建
16.5 shared_ptr 使用场景、陷阱、性能分析与使用建议#
16.5.1 shared_ptr 使用场景#
shared_ptr<int> createO(int value) {
return make_shared<int>(value); // 返回 shared_ptr
}
void myfunc(int value) {
shared_ptr<int> ptmp = createO(10);
return; // ptmp 离开作用域,内存自动释放
}
16.5.2 shared_ptr 使用陷阱分析#
[!tip] 陷阱1:慎用裸指针
- ! 把裸指针绑定到
shared_ptr后,==不应再使用裸指针==访问该内存- ! 不要用裸指针初始化==多个==
shared_ptr,否则会产生多个独立引用计数,导致重复释放
// 错误:裸指针初始化多个 shared_ptr
int *pi = new int;
shared_ptr<int> p1(pi);
shared_ptr<int> p2(pi); // p1、p2 各自引用计数为 1,释放时重复 delete!
// 正确
shared_ptr<int> p1(new int); // 直接传递 new
[!tip] 陷阱2:慎用 get 返回的指针
- !
get()返回的指针==不能delete==- ! 不能用
get()返回的指针初始化另一个shared_ptr
shared_ptr<int> myp(new int(100));
int *p = myp.get();
// delete p; // 绝对不可以!
// shared_ptr<int> myp2(p); // 绝对不可以!
shared_ptr<int> myp2(myp); // 正确:共享引用
[!tip] 陷阱3:用 enable_shared_from_this 返回 this
- ! 在类成员函数中直接用
this创建shared_ptr会导致==独立引用计数==- 解决:继承
std::enable_shared_from_this<类名>,使用shared_from_this()
class CT : public std::enable_shared_from_this<CT> {
public:
shared_ptr<CT> getself() {
// return shared_ptr<CT>(this); // 错误!
return shared_from_this(); // 正确
}
};
[!tip] 陷阱4:避免循环引用
- ! 两个类互相持有对方的
shared_ptr,会导致==循环引用==,引用计数永远不为 0,内存泄漏- 解决:将其中一个改为
weak_ptr
class CA;
class CB;
class CA {
public:
shared_ptr<CB> m_pbs; // 强引用 CB
~CA() { cout << "~A()" << endl; }
};
class CB {
public:
weak_ptr<CA> m_pas; // 改为弱引用!
~CB() { cout << "~B()" << endl; }
};
16.5.3 性能说明#
[!tip] shared_ptr 性能
- 尺寸为裸指针的 2 倍(8 字节 x86),包含对象指针和控制块指针
- 控制块创建时机:
- 1
make_shared创建时- 1 用裸指针创建
shared_ptr时- 指定删除器不影响
shared_ptr大小- 移动语义: 移动不会增加引用计数,复制会增加
shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1)); // 移动构造,p1 置空,p2 引用计数为 1
16.5.4 补充说明和使用建议#
[!tip] 使用建议
- 优先使用
make_shared构造智能指针(更高效,只分配一次内存)- 不要
new shared_ptr<T>- 不要
memcpy shared_ptr<T>- 不确定的用法要慎重分析
16.6 unique_ptr 简介与常用操作#
- @ unique_ptr图
- ![[白板/P51 unique_ptr.canvas|P51 unique_ptr]]
16.6.1 unique_ptr 简介#
[!tip] unique_ptr 概念
- @
unique_ptr: 独占式智能指针,同一时刻==只能有一个==指针指向该对象- 销毁时自动释放所指向的对象
- ==优先选择==
unique_ptr,不能满足需求时再考虑shared_ptr
// 常规初始化
unique_ptr<int> pi; // 空指针
unique_ptr<int> pi2(new int(105)); // 绑定 new 返回的指针
// make_unique(C++14)
unique_ptr<int> p1 = make_unique<int>(100);
auto p2 = make_unique<int>(200);
16.6.2 unique_ptr 常用操作#
[!tip] unique_ptr 不支持的操作
- ! 不支持==复制==:
unique_ptr<T> p2(p1)— 编译错误- ! 不支持==赋值==:
p2 = p1— 编译错误- 支持移动:
unique_ptr<T> p2(std::move(p1))
[!tip] unique_ptr 常用成员函数
函数 说明 release()放弃控制权,返回裸指针,智能指针置空 reset()不带参:释放对象并置空;带参:指向新对象 get()返回保存的裸指针(不要 delete)swap()交换两个智能指针所指向的对象 *p解引用(数组不支持) = nullptr释放对象并置空
// release 用法
unique_ptr<string> ps1(new string("hello"));
unique_ptr<string> ps2(ps1.release()); // ps1 置空,ps2 接管
// 注意:release 后必须处理返回的裸指针
string *tempp = ps2.release();
delete tempp; // 手动释放
// reset 用法
unique_ptr<string> prs(new string("hello"));
prs.reset(); // 释放并置空
prs.reset(new string("world")); // 指向新对象
[!tip] unique_ptr 管理数组
- 尖括号中加
[]即可管理数组,支持[]下标访问- ! 有析构函数的类,
<>中必须写T[],否则报异常
unique_ptr<int[]> ptrarray(new int[10]);
ptrarray[0] = 12; // 支持下标访问
unique_ptr<A[]> ptrarray(new A[10]); // 正确
// unique_ptr<A> ptrarray(new A[10]); // 异常!
[!tip] 转换为 shared_ptr
unique_ptr可以转换为shared_ptr(通过移动语义)- 转换后
unique_ptr置空,shared_ptr引用计数为 1- 系统会为
shared_ptr创建控制块
unique_ptr<string> ps(new string("hello"));
shared_ptr<string> ps2(std::move(ps)); // ps 置空,ps2 引用计数为 1
16.7 返回 unique_ptr、删除器与尺寸问题#
16.7.1 返回 unique_ptr#
[!tip] 函数返回 unique_ptr
unique_ptr不支持复制,但==从函数返回==是例外- 返回局部
unique_ptr时,编译器会调用移动构造函数
unique_ptr<string> tuniqp() {
return unique_ptr<string>(new string("hello"));
// 或者
// unique_ptr<string> pr(new string("hello"));
// return pr; // 也可以
}
unique_ptr<string> ps;
ps = tuniqp(); // 可以接收
16.7.2 删除器#
[!tip] unique_ptr 删除器
- 删除器类型写在尖括号中:
unique_ptr<对象类型, 删除器类型>- ! 删除器类型是
unique_ptr==类型的一部分==,不同于shared_ptr- 删除器不同则
unique_ptr类型不同,不能放入同一容器
// 函数指针类型作为删除器
typedef void(*fp)(string*);
unique_ptr<string, fp> ps1(new string("hello"), mydeleter);
// using 定义函数指针类型
using fp2 = void(*)(string*);
unique_ptr<string, fp2> ps2(new string("hello"), mydeleter);
// decltype 推导
auto mydella = [](string *pdel) { delete pdel; };
unique_ptr<string, decltype(mydella)> ps5(new string("hello"), mydella);
16.7.3 尺寸问题#
[!tip] unique_ptr 尺寸
- 通常情况下,
unique_ptr尺寸与==裸指针相同==(x86 下 4 字节)- 如果删除器是 lambda 表达式:尺寸==不变==
- 如果删除器是函数指针:尺寸==变大==(x86 下 8 字节)
- 与
shared_ptr不同:shared_ptr不管什么删除器,大小始终是裸指针 2 倍
16.8 智能指针总结#
[!tip] 智能指针设计思想
- 核心目的:==自动释放内存==,防止内存泄漏
- 智能指针在对象离开作用域时自动调用析构函数释放内存
[!tip] auto_ptr 为何被废弃
auto_ptr复制时会默默转移所有权,原指针变空,后续使用会崩溃unique_ptr复制时直接编译报错,更安全unique_ptr通过移动语义显式转移所有权
Reply by Email[!tip] 智能指针选择
- 优先考虑
unique_ptr(独占式,开销小)- 需要多个指针指向同一对象时选择
shared_ptrweak_ptr辅助shared_ptr解决循环引用问题- 不要再使用
auto_ptr

