C++ 深度解析教程(十八)拾遗:令人迷惑的写法、技巧:自定义内存管理、异 ...

打印 上一主题 下一主题

主题 660|帖子 660|积分 1980

六十八、拾遗:令人迷惑的写法

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实现方式


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表