《Effective C++ 55&35》随笔

Effective C++ 55&35 随笔


大概很有以前看过这两本书,不过那时候知道的也不太多,笔记在OneNote上也是乱七八糟的,后来因为某些原因在OneNote上的笔记怎么也找不到了,重新看一遍好好整理整理。
其中一些不怎么重要部分就被跳过了。

Effective C++ 55

  02条: 尽量以const enum inline 替换#define
    ①对于单纯常量最好以const对象或者enum代替define
    ②对于形似函数的宏改用inline函数替换
    首先define可能不被视为语言的一部分,其次define是替换,会在程序里替换多份,而const是变量,enum则是一个符号。

  03条: 尽可能使用const
    ①const帮助编译器侦测出错误用法。
    ②编译器强制实施bitwise,但编写程序应该使用“概念上的常量性”。
    ③const和non const成员函数有等价实现时,non const版本调用const版本可以避免代码重复。
    bitwise指的是 凡隶属对象的任何一个bit都不应该在const成员函数中被修改,但实际上可能有一个成员是指针,其指向的数据却可以被修改,因数据虽然被对象成员指向,却不属于对象,所以这里应该使用“概念上的常量性”,其所指的对象在const成员函数中也不可修改。

  04条: 确定对象使用前被初始化
    ①为内置雷响手工初始化,因为C++不初始化他们。
    ②构造成员函数最好使用成员初值列,而不在构造函数内使用赋值操作。使用初值列时的成员顺序应该与他们被声明的顺序相同。
    ③使用局部静态对象替换非局部静态对象 以解决 跨编译单元之初始化次序问题。
    跨编译单元初始化次序问题是:一个非局部静态对象,或者是全局对象,可能在另一个文件中被使用时,还未被初始化,因为c++对他们的初始化顺序是不确定的。在一个函数中将该对象声明为局部静态对象的形式,随后函数返回该对象。这样不论何时调用函数获取该对象时,其总能被初始化。

  05条: 编译器自动生成的函数
    ①编译器自动生成默认构造,复制构造,赋值,移动复制构造,移动赋值,析构。
    当用户自定义复制构造时,编译器不会生成移动复制构造,赋值操作一样。因为当用户自定义了复制构造函数,就意味着有需要自己完成的操作,默认生成的移动版本是不合适的。

  06条:将复制构造声明为private或者=delete来禁用他们

  08条: 别让异常逃离析构
    ①析构函数内部的异常应该自己处理掉。

  09条: 绝不再析构过程中调用虚函数
    ①不应该再析构过程中调用虚函数
    当一个对象进入析构函数,那么其就不再是一个完整的对象,任何成员都可能失效,从而调用虚函数导致未定义行为。

  10条: operator=返回自身的引用

  11条: operator=中处理自我赋值

  12条: 复制对象时勿忘复制其每一个成员
    ①复制构造函数应确保复制 对象内所有成员变量 以及 所有基类成分(调用基类的复制构造)
    ②不用一个复制构造实现另一个复制构造,应该将相同操作放进第三个函数。

  13条: 使用对象管理资源
    ①使用RAII可以防止资源泄露,其作为一个栈变量,在构造时获得资源,析构时释放资源。
    shared_ptr

  14条: 资源管理的复制行为
    ①复制RAII应该也复制它所管理的资源。
    ②普遍做法是:禁止复制,引用计数。

  15条: 资源管理类应该提供对原始资源的访问

  16条: 成对使用new和delete采取相同的形式
    new 和 new[]的行为是不一样的,int举例,new单纯申请一个4字节空间返回,new[3]则可能申请16字节,前四个字节用来存储长度,然后把申请到的地址+4返回给用户来填写数据。delete[]时,将归还的地址-4,得到长度,计算出申请的原始内存长度,然后释放。

  17条: 以独立语句将new的对象放入智能指针
    书中展示的代码:fun1(new A, fun2())
    可能出现的情况是,new到了地址后,然后调用fun2()来获取第二参数,但是fun2抛出了异常,随后导致了刚刚new到的内存泄露

  20条: 尽可能以 常量引用 代替 按值传递
    ①一方面常量引用更高效,另一方面常量引用可以避免对象切割。
    即基类引用引用派生类对象,按值传递则只复制派生类对象的基类部分。

  21条: 返回引用时
    ①不应该返回一个局部栈对象的指针或引用

  22条: 成员变量声明为私有
    ①私有权限提供了访问数据一致性,细微划分访问控制,设置约束条件,提供那个实现弹性。

  24条: 如果需要为某个函数的所有参数(包括隐喻的那个this),那么应当是一个非成员函数

  26条: 尽可能延后变量定义式出现的时间
    说实话对这一条不太认同,书中总是举出一些极端的例子,既然总要使用,代价总是要消耗,为什么不提前定义好,让之后的运行更紧凑呢,还有就是把变量定义到一起,清晰的写明注释,不是一目了然吗。

  27条: 尽量减少转型动作
    ①若转型是必要的,将转型放在某个函数后,由用户调用该函数。

                                      2020年10月15日23:37:08  

  28条: 避免返回handles
    ①handles包括引用 指针 迭代器 等,他们指向对象内部,当一个对象的成员函数将这些引用返回,那么外部取得到该引用后,可以随意修改对象

  29条: 异常安全
    ①大多数情况下,难以保证不抛出异常。所剩下的选择只有基本承诺和强烈保证。
    基本承诺保证异常抛出时,对象和数据不会因此损坏。
    强烈保证则在抛出异常时,返回程序先前的状态,通过copy and swap来实现

  30条: inline函数
    将inline限制在小型 被频繁调用的函数。
    inline只是一个申请,试图让编译器将其作为内联函数,编译器则会自己选择哪些函数成为inline。比如那些带循环递归的,或者是虚函数的,大多不会称为inline

  31条: 降低文件间的编译依存关系
    ①相依于声明式,而不是定义式。
    一个是使用指针和引用代替对象直接存在。另一个是使用虚基类定义接口,然后由此派生,基类指针指向派生类对象。

  32条: public继承而来的是is-a关系
    ①适用于基类对象的每一件事情在派生类对象上都适用。

  33条: 名称遮掩
    ①派生类中的名称会遮掩基类内的同名名称。
    如果一个基类成员函数被重载,派生类中却只复写了其中一个版本,那么基类的所有其他版本都被隐藏,使用 using base::fun;声明使其可用

  34条: 区分接口继承和实现继承
    ①公有继承下,派生类总是继承基类的接口。
    ②纯虚函数 实现了 接口继承。
    ③非纯虚函数 实现了 接口继承及其默认实现。
    ④非虚函数 实现了 接口继承和强制性实现继承。

  35条: 虚函数以外的其他选择
    ①NVI手法。指的是使用共有非虚函数 调用 私有虚函数。称为外覆器或者wrapper。
    ②函数包装器std::functional,这个确实体会到了,有时候碰到bind生成的函数和函数指针比匹配,使用包装器就可以很容易解决。

  36条: 绝不重新定义继承而来的非虚函数
    否则他应当是一个虚函数

  37条: 绝不重新定义继承而来的缺省参数值
    ①虚函数是动态绑定,缺省参数值是静态绑定。
    那么当一个基类指针指向派生比象,调用的是派生类版本的虚函数,使用的却是基类版本的默认参数。

                                      2020年10月16日09:12:10

  39条: 私有继承
    ①私有继承意味着“有一个”,类似复合的概念,除非派生类需要访问基类保护成员或者需要重定义虚函数,否则应该使用复合关系。

  40条: 多重继承
    ①虚继承导致增加对象大小、速度等等一系列成本。
    ②虚继承最理i想的情况是虚基类不带任何数据,只提供接口。
    多重继承而来的关系,可能继承相同的名称符号,包括但不限于函数 typedef等需要使用作用域解析符明确指出。
    钻石继承情况下,会有多个基类对象存在,使用虚继承则只会保留一个基类对象。

  41条: 隐式接口和编译器多态
    ①对类接口而言,接口是显示的,基于函数签名。多态通过虚函数发生于运行期。
    ②对于模板参数而言,接口是隐式的,基于有效表达式。多态通过模板具现化和函数重载解析发生于编译期。
    另外,隐式转换是一个值得注意的问题,隐式转换+模板,使得可以具现化的范围大大增加。

  42条: typename的双重含义
    ①一个是模板声明参数使用
    ②另一个则是b验明 嵌套从属类型。当如此 class::typename1,符号名typename1不会被视作一个类型,除非在它前面加上typename:typename class::typename1

  44条: 将参数无关代码抽离模板
    ①非类型模板参数如int等,可以将其作为成员来表示,从而避免部分代码膨胀。
    ②类型模板参数造成的代码膨胀让其拥有完全相同的二进制描述。
    第二条没怎么看懂,书里的操作大概是:模板的参数类型都更改为指针类型参数,然后通过void*类型实现模板,然后使用时再转型?

  45条: 成员函数模板(复制构造)
    ①当使用成员模板实现 泛化复制构造 或 泛化赋值时,还是需要提供一个正常的版本。

  49条: new-handler
    ①set_new_handler允许客户指定一个函数,再内存分配无法满足时被调用。并且返回先前的new handler函数。

  50条: 自定义 new 和 delete
    ①自定义new 和 delete可以提供很多操作,监控内存之类的。

  51条: 重载new delete时
    ①要注意处理0字节的申请以及空指针的delete

  52条: 写了placement new也要写placement delete

  53条: 不要忽视编译器警告
    处理掉所有警告大概会很有成就感?

                                      2020年10月16日22:14:46

More Effective C++ 35

  01条: 仔细区别指针和引用
    ①当知道需要指向某个东西,而且绝不会改变指向其他东西,使用引用,其他都应该使用指针。

  02条: 使用C++转型操作符
    ①static_cast(expr)    正常转型
    ②const_cast(expr)    去除常量性
    ③dynamic_cast(expr)    继承体系中向派生类转型
    使用C++转型的一个主要原因是清晰明确,容易辨识

  03条: 绝不以多态方式处理数组
    ①数组指针的计算是buf+sizeof(obj)来计算的,基类对象和派生类对象必然不一样大,计算数组偏移时发生未定义行为。
    ②另一种情况是通过基类指针删除派生类数组,是未定义的行为。

  04条: 非必要 不提供默认构造
    ①结论:添加无意义的默认构造,影响效率,成员函数需要测试字段是否有效。不提供默认构造虽然会带来一些限制,但也带来了保证,这样类对象总是被有效初始化,另一种意义上的效率。
    带来的限制有:无法直接建立对象数组,因为没有默认构造,也就无法初始化,但是可以通过指针数组来解决。

  05条: 对自定义的类型转换保持警觉
    ①自定义的转换带来了诸多的隐式接口,如Rational r(1, 2); cout << r;不需要Rational类重载<<,只需要其有一个operator int()或者operator double()之类的转换,那么编译器就会自动寻找这些转换。
    一个可行的办法是:不使用operator重载,使用成员函数显示转换以避免隐式接口。
    另一个是使用explicit。

  06条: 递增递减的前置后置形式
    ①前置operator++()    后置operator++(int)
    ②前置operator–()    后置operator–(int)

  07条: 不要重载&& || , 操作符
    ①骤死式语义 和 函数语义,如&&运算符,当运算符左边的的表达式为假,那么右边的表达式便不会计算,但是如果operator &&,其成为了一个函数,取得operator &&的结果需要完整的执行整个函数,带来了效率问题。
    ②逗号运算符存在的问题是:逗号运算符总算先计算左边,然后计算,若重载逗号,无法保证计算顺序。

  08条: 三种不同意义的new 和 delete
    ①new operator  对象产生与堆,同时在申请的内存上调用构造
    ②operator new  重载new操作,仅提供:自己分配内存的方式
    ③placement new 在已分配内存上调用一个构造函数
    需要注意的是,placement new既然提供了主动调用构造函数的自由,那么也需要承担主动调用析构的责任,否则可能导致持有的某些其他系统资源泄露,直到程序结束。

                                      2020年10月17日22:15:44

  09条: 利用析构防止资源泄漏
    ①其实主旨还是以对象管理资源,在发生异常栈解退时自动释放资源,不过对于目前的我来说还是喜欢直接操作原始资源,也不捕获异常,而是让他在异常时结束。

  10条: 在构造中防止资源泄露
    ①在构造中的异常是麻烦的,书中提到的解决办法是使用成员初值列+二段式构造,即构造函数只负责尽快将各个成员设为0之类的初值,使之尽早的成为一个 完整的对象,当期成为一个 完整的对象,便意味着可以在该对象上调用析构函数,接下来在身为 成员函数的二段构造中 执行那些可能抛出异常的危险行为,一旦抛出异常发生栈解退,那么就可以通过析构函数清理资源。
    ②但是如果这些危险操作在构造中执行,已经有一个成员诸如shared_ptr之类的获取到了资源,随后异常抛出,栈解退,但是因为构造函数未完成,其不是一个完整的对象,也就无法进入析构函数,那么shared_ptr成员的析构也不会调用,资源还是泄露了。
    PS:写了一段代码测试后(vs2019),发现不论是在构造中捕获异常,还是在调用端捕获异常,这个对象(假设A)的析构总是被调用,但是身为对象A 成员的shared_ptr成员 的析构却没有被调用。
    总结: 所以如果构造函数中发生异常,其行为是在栈解退时进入析构函数执行代码,但是却不会清理任何一个成员(调用析构),因为它不知道哪些成员是被设置好的,哪些是未被设置的。

  11条: 不让异常逃离析构,哪怕是捕获后什么都不做
    ①避免在栈解退过程中再次发生异常而被调用terminate
    ②确保析构执行完毕,类似10条中的行为,构造执行一半抛出异常,我不知道哪些成员被设置好,也就不知道哪些成员需要被清理(为成员调用析构)。同样在析构执行一半抛出异常,我不知道哪些成员的资源被释放了,哪些成员的资源没有被释放,也就意味这可能资源泄露。

  12条: 传递一个参数(或调用一个虚函数) 与 抛出一个异常的 差异
    ①调用函数是最佳匹配,而异常捕获是最先匹配(还会寻找转型以试图匹配)
    ②异常抛出的对象不论是引用还是什么,都会复制一份在传递(栈解退过程会导致对象失效),如果不是按引用而是按值捕获,会复制两次。
    ③被抛出的异常对象允许类型转换以完成捕获匹配

  13条: 按引用方式捕获异常
    ①首先最不应该抛出指针异常,也许是一个全局变量的地址,也许是一个局部变量的地址,也许是一个堆变量的地址,因为不知道来源,接下来是否应该delete掉是个问题。
    ②然后,如果按值捕获,异常捕获顺序又没有写好,则可能引起对象切割。

  14条: 异常声明
    void f1() throw()不抛出异常
    void f2() throw(int)抛出int异常
    ①可能出现的情况是,一个声明了不抛出异常的函数,调用了一个允许抛出异常的函数,那么就违反了调用者本身的异常安全声明,这会导致调用某个函数结束程序。
    ②不应该将异常声明和模板混用,处理可能的异常如:bad_alloc。

  17条: 缓式评估
    ①引用计数+写时复制。
    ②缓式取出。例如一个对象很大,且需要通过网络从远端数据库取出,只取得部分的数据存在对象,其余的等需要时再取出。
    ③缓式计算。例如要计算一个矩阵,可以使用哪里计算哪里,而不是全部计算。
    当然,如果所有数据总是要被使用,那么缓式评估也就是无意义的。

  19条: 临时对象的来源
    ①隐式转换。
    ②表达式计算。
    ③常量引用。

  20条: 协助完成返回值优化
    比如CLASSA fun(CLASSA a1, CLASSA a2){CLASSA a3 = a1*a2; return a3;}替换为CLASSA fun(CLASSA a1, CLASSA a2){return a1*a2;}如此做可以减少一个临时对象的生成

                                      2020年10月18日11:28:04

  22条: 以复合版本operator+=为基础实现独身版本operator+
    template <class T>
    const T operator+(const T& lhs, const T& rhs)
    {return T(rhs) += rhs}
    T(rhs)通过复制构造创建一个临时对象,然后该临时对象调用+=,然后将结果借由返回值优化返回

  24条: 了解虚函数、多重继承、虚基类、运行期类型识别的成本
    ①虚函数,虚函数表。凡声明或继承虚函数的类,都有一个自己的虚函数表,其中存储各个虚函数。凡声明虚函数的类,其对象都含有一个隐藏的数据成员,指向虚函数表。调用虚函数的成本和调用函数指针相似。**
    虚函数导致对象大小增加,inline几率降低。
    多重继承导致对象大小增加。
    虚拟继承导致对象大小增加。
    运行期类型识别导致类数据增加。

  26条: 限制对象产生的数量
    私有构造,单例

  27条: 要求或禁止对象产生于堆
    要求产生于heap时,私有化析构,用另一个函数包裹析构即可。
    禁止产生于heap时,operator delete(void*) = delete; operator new(size_t) = delete;

  28条: 智能指针

  29条: 引用计数

  33条: 非尾端类设计为抽象类

                                      2020年10月18日11:31:41

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值