六十八、拾遗:令人迷惑的写法
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的意义在于改变动态对象创建时的内存分配方式
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、异常处理深度解析
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、小结
- 异常规格说明可以看作接口的一部分
- 函数抛出的异常不在规格说明中,unexpected()被调用
- unexpected()中能够再次抛出异常
- 异常能够匹配,恢复程序的执行
- 否则,调用terminate()结束程序
七十二、动态内存申请的结果
1、动态内存申请的结果
- 必须知道的事实!
- malloc函数申请失败时返回NULL值
- new关键字申请失败时(根据编译器的不同)
- 返回NULL值
- 抛出std::bad_alloc异常
2、问题
- new 语句中的异常是怎么抛出来的?
- new 关键字在C++规范中的标准行为
- 在堆空间申请足够大的内存
- 成功:
- 在获取的空间中调用构造函数创建对象
- 返回对象的地址
- 失败:
- new关键字在C++规范中的标准行为
- new在分配内存时
- 如果空间不足,会调用全局的new_handler()函数
- new_handler()函数中抛出std::bad_alloc异常
- 可以自定义new_handler()函数
- new_handler()的定义和使用
3、问题
- 如何跨编译器统一new的行为,提高代码移植性?
- 解决方案
- 全局范围(不推荐)
- 重新定义new / delete 的实现,不抛出任何异常
- 自定义new_handler()函数,不抛出任何异常
- 类层次范围
- 单次动态内存分配
4、编程实验:动态内存申请
5、动态内存申请的结果
- 实验结论
- 不是所有的编译器都遵循C++的标准规范
- 编译器可能重定义new的实现,并在实现中抛出bad_alloc异常
- 编译器的默认实现中,可能没有设置全局的new_handler()函数
- 对于移植性要求较高的代码,需要考虑new的具体细节
- vs2010 new实现方式
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |