读书笔记:《More Effective C++》

More Effective C++

Basics

reference & pointer

  • reference 必定有值,pointer 可以为空
  • reference 声明时必须定义,必须初始化
  • reference 无需测试有效性,pointer 必须测试是否为 null
  • reference 可以更改指向对象的值,但是无法指向其他对象,pointer 可以指向其他对象

cast

  • c 转型:(type)expr
  • c++ 转型:cast(expr)
    • static:just like c 转型,无类型检查
    • dynamic:base-》derived,有类型检查
    • reinterpret:二进制重新解释,受制于编译期平台
    • const:amend costness or volatileness

polymorphically array

  • 使用base array pointer处理derived array,会导致未定义行为

default constructor

  • 贸然提供default constructor需要在运行时测试是否值有效
  • 缺乏default constructor会带来束缚
    • 无法创建对象数组,只能创建指针数组
    • 可以使用 placement new 创建对象数组,显式使用析构函数的 placement delete删除
      • 使用 delete 删除 placement new 创建的对象会导致未定义行为

Operators

implicit conversion

  • 单参数构造函数(或其余参数有默认值)
  • 隐式类型转换操作符
  • 隐式类型转换导致难以发现的问题:
    • 构造函数标记为 explicit
    • 定义显式类型转换:asType()
    • 只能进行一次隐式转换,多次类型转换不会有问题

increment/decrement & prefix/postfix

  • 【++i,累加后取出】:clazz& operator++()
    • 返回引用,允许++++i
  • 【i++,取出后累加】:const clazz operator++(clazz)
    • 额外参数区分,参数不得使用
    • 返回const,不允许i++++
    • 为了使得逻辑一致,用increment prefix来实现increment postfix

override &&、||、,

  • 重载&&、||导致问题:
    • &&、||,条件满足/不满足会提前终止
    • 重载后,左右式作为函数参数都会被执行
  • 重载,导致问题:
    • 逗号表达式,先求解左式,再求解右式,值为右式
    • 重载后,函数无法保证执行顺序

new, operator new, placement new, delete, operator delete, []

  • new:无法重载,operator new + constructor + placement new
  • operator new:重载分配内存的行为
    • void * operator new(size_t)
  • placement new:重载在指定内存上构建对象
    • void * operator new(size_t, void* location)
  • delete:destructor + operator delete
    • placement new 分配的内存无法通过 operator delete 删除
      • 显式析构 + 分配内存对应的方式删除
        • new->delete
        • malloc->free

Exceptions

  • 程序运行出错时设置状态变量或返回错误码,无法保证异常被程序处理,程序可能会异常运行;而抛出异常如果未进行捕获,程序便会立刻终止。

delete in destructor

  • 将资源分配在对象内,在析构函数中删除内存
  • 析构函数必然会被调用,防止因为异常产生未删除内存

exceptions in constructor

  • 构造函数异常,部分构造的对象不会自动析构
    • 将所有成员变量视为RAII对象,使用shared_ptr

exceptions in destructor

  • 使用uncaught_exception判断析构函数时候正有一个exception在作用中
  • 正在处理异常的析构函数抛出未捕获的异常,会导致程序终止
  • 不应该让析构函数抛出异常,析构函数应该是异常安全的
stack unwinding

C++异常处理,栈展开机制:

  1. 异常被抛出
  2. 当前作用域,搜索异常处理程序catch块
  3. 未搜索到,销毁该作用域内的局部对象
    1. 调用析构函数出现异常,程序终止,std::terminate
  4. 退出当前作用域,继续搜索上一级作用域
  5. 未处理异常,std::terminate,std::abort,生成核心转储文件

catch exceptions param pass

  • 处理异常和函数传参的区别
    • 异常永远会被复制,因为异常处理程序超出函数调用作用域
      • 按值捕获,复制两次,传参一次,复制一次
      • 引用捕获,复制一次
    • 不发生隐式类型转换,仅能发生继承类型转换
    • 异常按catch的顺序处理,没有最优类型匹配
      • 子类异常应该写在基类异常的前面,否则不会被处理

catch exceptions by reference

  • catch by pointer:无法判断是否应该删除资源
  • catch by value:子类异常由基类捕获会导致对象切割,仅剩下基类行为
  • catch by reference:推荐使用,且仅复制一次

exception specifications

  • 在定义中指定函数可能抛出的异常
    • 未指定则表明可能抛出任何异常
    • 指定noexcept表明不会抛出任何异常
  • 抛出未定义异常会导致unexpected函数调用
    • unexpected的默认行为是调用terminate
    • 可以使用set_unexpected自定义处理行为
  • 可以提供额外的异常定义说明,但是违反说明的异常会直接导致程序终止

exception handling cost

  • 异常处理会使得程序效率下降5%~10%

Efficiency

80-20 rule

  • 80%的资源被20%的代码占用,不仅需要大幅优化代码,更重要的是找到那20%的瓶颈
  • 不根据经验和直觉,而是使用profiler来分析那20%代码

lazy evaluation

  • 如果计算不是绝对必要,使用lazy evaluation替代eager evaluation

over-eager evaluation

  • cache:缓存已计算的值
  • prefetch:预先从磁盘读取可能访问的值,每次读取磁盘多于请求量的值

avoid temporary

  • 临时对象≠局部对象,临时对象是没有名字的栈对象
    • 函数传参,隐式类型转换
      • by value:隐式转换为临时对象
      • by const reference:隐式转换为临时对象
      • by non-const reference:隐式转化无法被触发,因为对临时对象的修改无法反馈会引用对象,因此编译报错
    • 函数返回值
      • 使用+=替代+
      • 返回值优化RVO

return value optimize

  • 直接返回构造函数,避免临时对象的生成

overload to avoid implicit type conversions

  • 使用函数重载来避免没有函数重载时的隐式类型转换

supply += and +

  • 操作符+=往往比+有更高的效率
  • 重载操作符的时候提供+=版本

third party library

  • 相同功能的第三方库,在性能方面,可能会有不同的设计,和不同的效率偏重

inherit cost

  • virtual functions:
    • 每个class包含一个虚函数表
    • 每个对象包含一个虚函数表的指针
    • virtual 函数无法 inline,因为 inline 需要在编译期复制函数调用,而 virtual 需要运行期才能确定调用函数
  • multiple inheritance & virtual base classes
    • 菱形继承需要virtual base class,额外的指针开销
  • runtime type identification,RTTI
    • typeid获取class相应的type_info,需要额外的指针指向type_info
    • 可以在虚函数表中添加指向type_info的指针

Techniques

virtual constructor & non-member function

  • virtual constructor
    • factory function 根据参数生成不同的子类
  • virtual copy constructor
    • 每个子类添加虚函数clone调用拷贝构造函数
  • virtual non-member function
    • 每个子类添加虚函数实现具体操作
    • non-member function inline化,调用相应虚函数

limit class instance count

  • 关键资源有限,其对应的类示例数目也有限
    • 零或一:单例模式
    • n:在构造函数中添加static示例计数,使用make函数显式构造,防止隐式构造增加计数

fore constructor in/out of heap

  • in heap:对象有自杀能力,delete删除内存
  • out of heap:对象不会造成内存泄漏
  • 重载:
    • static void * operator new(size_t)
    • static void operator delete(void *)

smart pointers

  • 智能指针无法在子类和基类之间转换,需要借助于隐式类型转换操作符

reference count

  • 多数对象共享少数的实值,实值的产生和删除成本高,使用引用计数来记录共享对象的个数,在引用计数为零时删除

proxy class

  • 表示一个观念上不存在的对象
    • 实现[][]重载,proxy对象为Array1D

multi virtual function

  • 根据多个子类的类型判断调用的函数
    • func(base*, base*)
      • 虚函数+RTTI,运行时类型辨识
      • 虚函数重载
      • 自行实现虚函数表

Miscellany

相关推荐
TT哇10 分钟前
【前缀和】矩阵区域和(medium)
java·线性代数·算法·矩阵
JoshuaGraham22 分钟前
Java 并发-newFixedThreadPool
java·开发语言
darkchink25 分钟前
[LevelDB]Block系统内幕解析-元数据块(Meta Block)&元数据索引块(MetaIndex Block)&索引块(Index Block)
android·java·服务器·c语言·数据库·c++·分布式
iFlyCai32 分钟前
Xcode警报“Ignoring duplicate libraries: ‘-lc++’” 警报
开发语言·c++
不吃酸的柠檬35 分钟前
如何在idea中新建一个项目
java·ide·intellij-idea
程序员鱼皮39 分钟前
2025全新 Java 面经:字节飞书实习一面真实复盘,附答案整理+ Java 题库!
java·后端·面试
Freak嵌入式41 分钟前
一文速通 Python 并行计算:06 Python 多线程编程-基于队列进行通信
开发语言·python·多线程·面向对象·并行计算
无名之逆1 小时前
[特殊字符] 超轻高性能的 Rust HTTP 服务器 —— Hyperlane [特殊字符][特殊字符]
java·服务器·开发语言·前端·网络·http·rust
xiecoding.cn1 小时前
Sublime Text使用教程(用Sublime Text编写C语言程序)
c语言·c++·青少年编程·编辑器·sublime text