ToB企服应用市场:ToB评测及商务社交产业平台
标题:
C++ 深度解析教程(十八)拾遗:令人迷惑的写法、技巧:自定义内存管理、异
[打印本页]
作者:
缠丝猫
时间:
2022-6-25 08:52
标题:
C++ 深度解析教程(十八)拾遗:令人迷惑的写法、技巧:自定义内存管理、异
六十八、拾遗:令人迷惑的写法
1
、令人迷惑的写法
下面的程序想要表达什么意思?
历史上的原因。。。
早期的C++直接复用class关键字来定义模板
但是泛型编程针对的不只是类类型
class关键字的复用使得代码出现二义性
typename诞生的直接诱因
自定义类类型内部的嵌套类型
不同类中的同一个标识符可能导致二义性
编译器无法辨识标识符究竟是什么
2
、编程实验:模板中的二义性
3
、令人迷惑的写法
typename的作用:
在模板定义中声明泛指类型
明确告诉编译器其后的标识符为类型
4
、下面的程序想要表达什么意思?
5
、令人迷惑的写法
try ... catch 用于分隔正常功能代码与异常处理代码
try ... catch 可以直接将函数实现分隔为2部分
函数声明和定义时可以直接指定可能抛出的异常类型
异常声明成为函数的一部分可以提高代码可读性
函数异常声明的注意事项
函数异常声明是一种与编译器之间的契约
函数声明异常后就只能抛出声明的异常
抛出其它异常将导致程序运行终止
可以直接通过异常声明定义无异常函数
6
、编程实验:新的异常写法
7
、小结
class可以用来在模板中定义泛指类型(不推荐)
typename是可以消除模板中的二义性
try...catch可以将函数体分成2部分
异常声明能够提供程序的可读性
六十九、技巧:自定义内存管理
1
、笔试题
统计对象中某个成员变量的访问次数
2
、遗失的关键字
mutable是为了突破const函数的限制而设计的
mutable成员变量将永远处于可改变的状态
mutable在实际的项目开发中被严禁滥用
mutable的深入分析
mutable成员变量破坏了只读对象的内部状态
const成员函数保证只读对象的状态不变性
mutable成员变量的出现无法保证状态不变性
3
、编程实验:成员变量的访问统计
4
、面试题
new关键字创建出来的对象位于什么地方?
5
、被忽略的事实
new / delete的本质是C++预定义的操作符
C++对这两个操作符做了严格的行为定义
new :
获取足够大的内存空间(默认为堆空间)
在获取的空间中调用构造函数创建对象
delete:
调用析构函数销毁对象
归还对象所占用的空间(默认为堆空间)
在C++中能够重载new / delete操作符
全局重载(不推荐)
局部重载(针对具体类进行重载)
重载new / delete的意义在于改变动态对象创建时的内存分配方式
new / delete的重载方式
6
、编程实验:静态存储区中创建动态对象
7
、面试题
如何在指定的地址上创建C++对象?
8
、设计思路
解决方案
在类中重载new / delete操作符
在new的操作符重载函数中返回指定的地址
在delete操作符重载中标记对应的地址可用
9
、编程实验:自定义动态对象的存储空间
10
、被忽略的事实
new[] / delete[]与new / delete完全不同
动态对象数组创建通过new[]完成
动态对象数组的销毁通过delete[]完成
new[] / delete[]能够被重载,进而改变内存管理方式
new[] / delete的重载方式
注意事项
new[]实际需要返回的内存空间可能比期望的要多
对象数组占用的内存中需要保存数组信息(数组长度)
数组信息用于确定构造函数和析构函数的调用次数
11
、编程实验:动态数组的内存管理
12
、小结
new / delete的本质为操作符
可以通过全局函数重载new / delete(不推荐)
可以针对具体的类重载new / delete
new[] / delete[] new / delete 完全不同
new[] / delete[]也是可以被重载的操作符
new[]返回的内存空间可能比期望的要多
七十、异常处理深度解析
1
、异常处理深度解析
问题
如果在main函数中抛出异常会发生什么?
如果异常不处理,最后会传到哪里?
下面的代码输出什么?
2
、编程实验:异常的最终处理?
3
、异常处理深度解析
如果异常无法被处理,terminate()结束函数会被自动调用
默认情况下,terminate()调用库函数abort()终止程序
abort()函数使得程序执行异常而立即退出
C++支持替换默认的terminate()函数实现
terminate()函数的替换
自定义一个无返回值无参数的函数
不能抛出任何异常
必须以某种方式结束当前程序
调用set_terminate()设置自定义的结束函数
参数类型为void (*)()
返回值为默认的terminate()函数入口地址
4
、编程实验:自定义结束函数
5
、面试题
如果析构函数中抛出异常会发生什么情况?
6
、编程实验:析构函数抛出异常
7
、小结
如果异常没有被处理,最后terminate()结束整个程序
terminate()是整个程序释放系统资源的最后机会
结束函数可以自定义,但不能继续抛出异常
析构函数中不能抛出异常,可能导致terminate()多次调用
七十一、函数的异常规格说明
1
、函数的异常规格说明
问题
如何判断一个函数是否会抛出异常,以及抛出哪些异常?
C++提供语法用于声明函数所抛出的异常
异常声明作为函数声明的修饰符,写在参数列表后面
异常规格说明的意义
提示函数调用者必须做好异常处理的准备
提示函数的维护者不要抛出其它异常
异常规格说明是函数接口的一部分
2
、问题
如果抛出的异常不在声明列表中,会发生什么?
下面的代码输出什么?
3
、编程实验:异常规格之外的异常
4
、函数的异常规格说明
函数抛出的异常不在规格说明中,全局unexpected()被调用
默认的unexpected()函数会调用全局的terminate()函数
可以自定义函数替换默认的unexpected()函数实现
注意:不是所有的C++编译器都支持这个标准行为
unexpected()函数的替换
自定义一个无返回值无参数的函数
能够再次抛出异常
当异常符合触发函数的异常规格说明时,恢复程序执行
否则,调用全局terminate()函数结束程序
调用set unexpected ()设置自定义的异常函数
参数类型为void (*)()
返回值为默认的unexpected()函数入口地址
5
、编程实验:自定义
unexpected()
函数
6
、小结
C++中的函数可以声明异常规格说明
异常规格说明可以看作接口的一部分
函数抛出的异常不在规格说明中,unexpected()被调用
unexpected()中能够再次抛出异常
异常能够匹配,恢复程序的执行
否则,调用terminate()结束程序
七十二、动态内存申请的结果
1
、动态内存申请的结果
问题
动态内存申请一定成功吗?
常见的动态内存分配代码
必须知道的事实!
malloc函数申请失败时返回NULL值
new关键字申请失败时(根据编译器的不同)
返回NULL值
抛出std::bad_alloc异常
2
、问题
new 语句中的异常是怎么抛出来的?
new 关键字在C++规范中的标准行为
在堆空间申请足够大的内存
成功:
在获取的空间中调用构造函数创建对象
返回对象的地址
失败:
抛出std::bad_alloc异常
new关键字在C++规范中的标准行为
new在分配内存时
如果空间不足,会调用全局的new_handler()函数
new_handler()函数中抛出std::bad_alloc异常
可以自定义new_handler()函数
处理默认的 new内存分配失败的情况
new_handler()的定义和使用
3
、问题
如何跨编译器统一new的行为,提高代码移植性?
解决方案
全局范围(不推荐)
重新定义new / delete 的实现,不抛出任何异常
自定义new_handler()函数,不抛出任何异常
类层次范围
重载new / delete,不抛出任何异常
单次动态内存分配
使用nothrow参数,指明new不抛出异常
4
、编程实验:动态内存申请
5
、动态内存申请的结果
实验结论
不是所有的编译器都遵循C++的标准规范
编译器可能重定义new的实现,并在实现中抛出bad_alloc异常
编译器的默认实现中,可能没有设置全局的new_handler()函数
对于移植性要求较高的代码,需要考虑new的具体细节
vs2010 new实现方式
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4