读书笔记 | Effective C++:改善程序与设计的55个具体做法

《Effective C++》

PART0、前言


  • TOPIC
    • 运用c++进行高效编程
  • 收获
    • 了解c++如何行为
    • 为什么那样行为
    • 如何运用其行为形成优势

PART1、导读


定义式的任务

  • 对象:为此对象拨发内存的地点
  • 函数:提供代码本体
  • 类:列出他们的成员

c++没有接口的关键字,定义接口就是定义一个全是纯虚函数的类
PS:自定义构造函数后,编译器就不会自动构造默认构造函数

PART2、让自己习惯C++


视c++为一个语言联邦

  • 联邦组成
    • c
    • Object-Oriented C++
    • Templete C++
    • STL
  • 内置类型值传递通常比引用传递高效的原因
  • 传递效率
    • 引用传递本质传递一个指针:不管何种类型指针,默认是4字节
  • 执行效率
    • 直接寻址&间接寻址

尽量以const、enum、inline替换#define

  • 即“宁以编译器替换预处理器”
  • 单纯常量:以const或enum替换#define
  • 形似函数的宏,最好改用inline函数替换#define

尽可能使用const

const语法

  • 星号左侧出现const:被指物是常量
    const int *ptr
  • 星号右侧出现const:指针本身是常量
    int * const ptr
  • 右侧两式等价
    const int * ptr
    int const * ptr

const成员函数

  • void fun() const;
  • 声明const的理由
    • 让编译器知道,该函数不改变类成员变量
    • 提高代码健壮性
  • 两个函数如果只是常量性不同,可以被重载
    • const char& operator [] (int position) const;
    • char& operator [] (int position);

确定对象被使用前已先被初始化

  • c++对对象的初始化反复无常,不能保证始终初始化
  • 策略
    • 内置类型:使用对象前手工完成初始化
    • 引用类型:初始化责任落在构造函数上
      • 构造函数中使用形如:myName = name,并非初始化,而是赋值
        • 因为调用带参构造函数前,首先调用默认构造函数
      • 正确初始化:使用成员初始化列表的做法

PART3、构造、析构、赋值运算


条款05、了解C++默默编写并调用哪些函数

  • 如无声明,编译器会为类默默声明
    • copy构造函数
    • copy assignment 操作符
    • 析构函数

条款06、若不想使用编译器自动生成的函数,就该明确拒绝

  • 如何拒绝
    • 主动声明,且将其声明为private
    • 为防止友元或成员函数调用,故意不实现函数
    • 使用像Uncopyable这样的base class
      •   该方式是上述两点的综合
        
  • 用了上述方式
    • 企图拷贝、赋值时,编译器报错
  • 友元、成员函数调用时,链接器报错

条款07、为多态基类声明virtual析构函数

  • class只有有虚函数,一般也要有一个虚析构函数
    • 原因:防止只销毁base部分,derived部分没被销毁
  • 注意:把string、vector、list等标准库中不带virtual析构函数的类当做base class
  • c++没有类似java中final或c#中sealed的关键字

条款08、别让异常逃离析构函数

  • 最好不要在析构函数中抛异常
  • 实在需要在析构函数中执行动作,该如何避免
    • 调用abort
    • 吞下异常
    • 提供普通函数执行动作

条款09、绝不在构造、析构函数中调用virtual函数

  • 因为这样不能实现多态,只会调用base的函数
    • 派生类构造函数,一般先调用父类构造函数
    • base class构造期间,virtual函数不是virtual函数
    • 在derived class对象的base class构造期间,对象的类型是base class,而不是derived class
  • derived class析构函数开始执行时,相应的derived class成员变量便呈现未定义值,派生类先执行派生类析构函数,再执行基类析构函数

*条款10、令operator= 返回一个reference to this

  • 原因:实现连续赋值
  • 例子:x = y = z = 15
  • 该条款只是个协议,无强制性

条款11、在operator=中处理“自我赋值”

  • 例子:int a; a = a;
  • 例子:p =q(两个指针指向同一变量)

条款12、复制对象时勿忘其每一个成分

  • class每添加一个成员变量
    • 修改拷贝构造函数
    • 修改copy assignment操作
  • copy构造和copy assignment操作符有相近的代码
    • 可建立新的成员函数给两者调用

PART4、资源管理


条款13、以对象管理资源

  • 资源

    • 资源由系统给予,用完须还给系统
    • 常使用的资源
      1、内存(动态分配内存)
      2、文件描述器
      3、互斥锁
      4、数据库连接
      5、网络sockets
  • 有new,就要delete,有malloc,就要free

    • 但这还不够,因为代码可能因种种原因没运行到delete或free
    • 出现异常的情况
      1、区域内有一个过早return语句
      2、delete位于循环内,程序过早break或goto,跳出了循环
      3、区域内抛出异常
    • 上述异常情况,如果是个人开发,谨慎一点依然可以防止错误,但团队开发不行
      • 单纯依赖“对象总是会执行delete语句的”是行不通的
  • 策略:把资源放进对象内

    • 依赖c++的析构函数自动调用机制来确保资源被释放
      • 在析构函数中对对象调用delete
    • 关键想法
      1、获得资源后立刻放进管理对象
      2、运用析构函数确保资源被释放
    • 存放资源的对象即是智能指针
      • auto_ptr
        • 切记不能让多个auto_ptr同时指向同一个对象,防止野指针
        • 不同寻常的性质
          • 通过拷贝构造函数或opertor=复制的话,原指针变成null,复制所得指针取得资源唯一拥有权
          • 这一性质使得其并非动态分配资源的最佳对象
          • STL容器要求其元素有正常的复制行为,因此这些容器不能* 用auto_ptr
      • shared_ptr
        持续追踪共有多少对象指向资源
        无人指向时自动删除资源
        引用计数型
      • c++中没有特别针对“动态分配数组”而设计的智能指针
        • auto_ptr和shared_ptr在析构函数中做delete而不是delete[]
        • 原因是c++中有vector和string对象取代数组
        • 实在要用的话,可求助boost
          • boost::scoped_array
          • boost::shared_array
    • 为防止资源泄漏,使用智能对象,在构造函数中获得资源,在析构函数中释放资源

条款14、在资源管理类中小心copy行为

小心copy行为的几种做法

  • 禁止复制
    继承ucopyable,详见条款6
  • 对底层资源使用“引用计数法”
    shared_ptr的做法
  • 复制底部资源
    深度拷贝
  • 转移底部资源的拥有权
    autor_ptr的做法

条款15、在资源管理类中提供对原始资源的访问

  • 智能对象应该提供一个取得原始资源的方法
  • 对原始资源的访问经由显式转换(安全)或隐式转换(方便客户)

条款16、成对使用new和delete时要采取相同形式

  • 使用new生成对象,有两件事会发生
    1、内存被分配出来
    2、对此内存,有一个以上的构造函数被调用
  • 使用delete释放对象,也有两件事发生
    1、对此内存,有一个以上的析构函数被调用
    2、内存被释放
  • new时使用[],delete时也使用[]
  • ps:尽量不要对数组形式做typedef动作

条款17:以独立语句将newed对象置入智能指针

  • 以独立语句将newed对象存储于智能指针内,防止异常抛出,导致资源泄漏
  • 原型:std::tr1::shared_ptr<Widget> pw(new Widget)

PART5、设计与声明


条款18、让接口容易被正确使用,不易被误用

  • 尽量让你的type与内置type一致
    • 接口一致性
  • 阻止误用的方法
    • 建立新类型
    • 限制类型上的操作
    • 束缚对象的值
    • 消除客户的资源管理责任
  • 支持定制型删除器:防止DLL问题
    • 引用计数为0是调用删除器

条款19、设计class犹如设计type

高效设计type的规范

  • 新type对象应该如何被创建和销毁
    • 构造函数(内存分配函数)设计
    • 析构函数(内存释放函数)设计
  • 对象初始化和赋值有什么差别
  • 什么是新type的合法值
  • 新type的对象如果被pass by value,意味着什么
  • 详见P84-85

条款20、宁以常量引用传递替换值传递

  • 该规则不适用于内置类型以及STL中的迭代器和函数对象
    • 对于这些小型对象,执行效率与传递效率均比常量引用传递高
  • 对于引用类型,则适用该规则
    • 常量引用传递减少构造对象的成本
    • 还可避免对象切割问题
      • 但能调用derived class中的成员吗?

条款21、必须返回对象时,别妄想返回其reference

  • 函数返回一个引用或者指针
    • 如果指向local对象,将发生内存泄露
    • 如果指向动态对象(存于heap)
      • 对象的delete操作不明确,同样会造成资源泄漏
    • 如果指向static对象,则可能发生线程安全
  • 正确策略:返回对象
    • 构造与析构成本代价高,但一般编译器有做优化,可消除这一代价

条款22、将成员变量声明为private

  • 为什么不是public?
    1、一致性:统一函数均为public,变量为private
    这样用户使用该类均是调用函数,不用犹豫该不该加()
    2、精确控制:可实现不准访问,读写访问,只读访问
    3、封装:成员变量改变时,外部代码不会受到破坏
  • protected成员封装性不一定高于public~
    • 取决于代码破坏量

条款23、宁以non-member、non-friend替换member函数

  • 误解:写成member函数可以提高封装性,其实反而降低了封装性
  • 例子
  • 使用non-member、non-friend,增加
    • 封装性
    • 包裹弹性
    • 机能扩充性

条款24、若所有参数皆需类型转换,请为此采用non-member函数
条款25、考虑写出一个不抛异常的swap函数

  • swap传入的两个参数是
    • 对象形式的话,使用临时变量方法无可厚非
    • 指针的话,依然使用临时变量方法效率太低
      • 策略:自己写一个成员函数

PART6、实现


实现须知

  • 太快定义变量
    • 造成效率上的拖延
  • 过度使用转型
    • 代码变慢又难维护
    • 招来难以理解的错误
  • 返回对象内部数据的地址
    • 破坏封装
    • 留给客户虚吊号码牌(野指针)
  • 未考虑异常带来的冲击
    • 资源泄漏
    • 数据败坏
  • 过度inlining
    • 引起代码膨胀
  • 过度耦合
    • 不尽人意的冗长build时间

条款26、尽可能延后变量定义式的出现时间

  • 变量定义式与真正使用该变量的间隔要尽可能短
  • 防止间隔中代码抛异常,白白付出了变量的构造和析构成本

条款27、尽量少做转型动作

转型语法

  • c风格(旧式转型)
    1、( T )expression
    2、T( expression )

  • c++风格

    • const_cast<T>( exp )
      • 常量性转除
      • 唯一有此能力c++风格方式
    • dynamic_cast<T>( exp )
      • 安全向下转型
      • 决定某对象是否归属继承体系中的某个类型
      • 旧式转型无法执行
      • 运行成本高
      • 应用场合
        • 在derived类对象上执行操作函数,但手上只有base的指针或引用
    • reinterpret_cast<T>( exp )
      • 执行低级转型
        • 低级转型:int指针转型为int
      • 实际效果取决于编译器
    • static_cast<T>( exp )
      • 强迫隐式转换
      • 可执行上述多种转换的反向转换
      • 例子
        • non-const对象转const对象
        • int转double(向上转)
        • void*指针 转 typed指针
        • pointer2base 转 pointer2derived
        • 无法将const转non-const,只有const_cast能
  • 旧式转型仍合法

  • 新式转型较受欢迎的原因
    1、易辨识,易排bug
    2、转型动作目标窄化,编译器更容易诊断错误

  • 错误观念:
    转型什么也没做,只是告诉编译器把某类型视为另一类型

  • c++中,如果派生类中虚函数第一个动作是调用base class的对应函数,其原型:

    • base::vir();
    • 错误做法
      • 类型转换:static_cast<base>(*this).vir()
      • Java后遗症:super();
  • 优秀的c++代码很少使用转型

条款28、避免返回handles指向对象内部成分

  • 形式:返回handles(reference、指针、迭代器)指向private内部数据
  • 危害:造成调用者可通过引用更改内部数据
  • 预防:加const
    • 但依然有后遗症:导致悬垂指针、handle

条款29、为异常安全而努力是值得的

  • 带有异常安全性的函数会
    • 不泄漏任何资源
    • 不允许数据破坏
  • 异常安全函数保证
    • 基本承诺
      • 异常被抛出,程序内任何事物仍保持有效状态
    • 强烈保证
      • 异常抛出,程序状态不改变
      • 函数要么完全成功,要么完全失败,没有部分成功
      • 实现形式:copy and swap
      • 类似数据库事务的roll back
      • 类似怀孕,要么怀了,要么没怀,木有部分怀孕的说法
    • 不抛掷保证
      • 承诺绝不抛出异常
      • 即是什么都不做?

条款30、透彻了解inlining的里里外外

  • inline函数
    • 看着像函数,动作像函数,但又不需承受函数调用的开销
    • 编译器会对inline函数有特殊照顾
      • 对inline函数执行最优化
    • 比宏好的多
    • 缺点
      1、增加目标代码大小
      2、代码膨胀导致额外的换页行为,降低高速缓存的命中率
  • 错误观念:函数模板一定必须是inline
    • template的具现化与inline无关
    • 如果想让template内联化,请显示声明inline
  • 一般虚函数会使inlining落空
    • virtual意味着等待,直到运行期才确定调用那个函数
    • inline意味着执行前,先将调用动作替换为被调用函数的本体
  • 使用函数指针进行的调用,不会inlining
  • 构造/析构函数不适合inlining,特别是成员变量特多的类
  • inline函数无法调试
    • vs2010下是可以的~~
  • inline应用场合
    • 小型、被频繁调用的函数

相关细节


  • 许多环境中,template定义式通常只能置于头文件内,无法分离编译
    • 另有些环境支持分离编译,但很少
    • 如果编译器支持export关键字,也可实现分离编译
      • 但支持的编译器也很少

PART7、继承与面向对象设计


条款31、将文件间的编译依存关系降至最低

  • 使用引用对象、指针对象可以完成任务,就不要直接使用对象
  • 尽量以class声明式替换class定义式
  • 使编译依存性最小化的方式
    • handle class
    • interface class

条款32、确定你的public继承塑模出is-a关系

  • 适用于base class的事情一定也适用于derived class
  • 每个derived class 对象也都是一个base class对象

条款33、避免遮掩继承而来的名称

  • derived class内实现虚函数写不写virtual关键字
  • 派生类的名称会遮掩基类的名称
  • 让基类被遮掩的名称再见天日的方法
    • using 声明式
    • 转交函数

条款34、区分接口继承、实现继承

  • 纯虚函数只具体指定接口继承
  • 虚函数具体制定接口继承和缺省实现继承
  • 一般函数具体指定接口继承以及强制性实现继承

条款35、考虑virtual函数以外的其他选择

  • Non-Virtual-Interface实现
    • 将原来的虚函数改为普通public成员函数
    • 普通成员函数调用私有虚函数

条款36、绝不重新定义继承而来的non-virtual函数
条款37、绝不重新定义继承而来的缺省参数

  • 静态绑定又称前期绑定
  • 动态绑定又名后期绑定
  • 基类和派生类中同一函数均有缺省参数值的后果
    • 基类和派生类各出一半力
    • 缺省参数值是静态绑定,而虚函数要求的是动态绑定
    • 原因:程序执行速度和编译器实现的简易度

条款38、通过复合塑模处has-a或根据某物实现出

  • 复合两意义
    • has-a
    • is-implemented-in-terms-of
      • 例子:set和list的关系

条款39、明智而审慎的使用private继承

  • private继承的话,编译器不会自动将一个派生对象转换为一个base对象
  • private继承是为了采用base已经准备的某些特性,不意味base和derived之间有什么关系
  • private是一种实现技术
    • 无设计层面上的意义
    • 其意义只及于软件实现层面

条款40、明智而审慎的使用多继承

  • 多继承会造成多种后患
    • 继承多个base,如果base中有相同的名称,会导致歧义
    • 钻石型多重继承
      多个base又都继承同一个base
      virtual继承会增加成本
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容