《Effective C++》读书笔记

条款01:视C++为一个语言联邦

C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

C++可分为四个次语言:C,Object-Oriented C++,Template C++,STL。

条款02:尽量以const,enum,incline替换#define

对于单纯常量,最好以const对象或者enum替换define

对于形似函数的宏,最好改用incline函数替换define

const代替define:

使用const可以将变量视为常量,使其可以进入记号表中,而#define只能单纯的在编译前将变量替换成常量,有时还可能因为#define的括号带来一些意想不到的错误。

#define没有作用域,只能作用于全局,不能放在类中,而const可以作为类中的一个成员,如果想只有一份文件,那么可以加上static关键字。

#define只能表示变量,不能表示指针,而const不仅能表示不能修改的指针和指物,还能表示既不能修改的只能也不能修改的指物。

enum代替define

enum hack补偿法:一个属于枚举类型的数值可权充int类型被使用。

incline代替define

如果是define定义的较短的函数则可以使用incline内联函数的方法将define替换,不过这仅限于比较短的函数(小于十句语句)。

条款03:尽可能使用const

将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。

当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

const可以修饰任何,包括文件、函数、区块作用域中被声明为const的变量或者指针、指物或两者都是const。const放在指针星号(*)的左边表示指物是常量,放在右边表示指针是常量。

将函数返回值定义成const(const放在函数头前)可以防止误操作修改了返回值,将函数定义成const函数(const放在函数头尾)可以防止修改函数中在函数中修改变量。mutable可以解决这一问题,在变量声明前加mutable后可以在const函数中修改函数对象。const类型数据优先进入const函数,non-const类型数据优先进入non-const函数。

条款04:确定对象被使用前已先被初始化

为内置类型对象进行手工初始化,因为C++不保证初始化它们。

构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

为免除“跨编译器单元之初始化次序”问题,请以local static对象替换non-local static对象

赋值≠初始化,对于内置类型初始化和赋值成本相同。对于自定义类型通过构造函数初始化,通过default构造函数对数据设初值,然后立刻再对它们赋新值。

通过成员初始化列表对其赋值(成员初始化列表的次序总是和类中成员次序相同),在初始化数组时,必须先将指定数组大小的值初始化。

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

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

对于空类,如果自己没有声明,编译器就会自己声明一个copy构造函数,一个copy assignment操作符和一个析构函数,此外如果你没有声明任何构造函数,编译器就会声明一个default构造函数。所有这些函数默认都是public且incline的。唯有当这些函数被需要时(被调用),它们才会被编译器创建出来。

注意编译器产出的析构函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数(这种情况下这个函数的虚属性;virtualness;主要来自base calss)。

C++并不允许“让reference改指向不同的对象”。面对这个难题C++的响应时拒绝编译那一行赋值动作。如果打算在一个“内含reference成员”的class内支持赋值操作,那么你必须自己定义copy assignment操作符。

面对“内含const成员”更改const是不合法的,所以编译器不知道在如何在它自己生成的赋值函数内面对它们。

如果某个base class将copy assignment操作符声明为private,编译器将拒绝为其derived class生成一个copy assignment操作符。毕竟编译器为derived classes所生的copy assignment操作符想象中可以处理base class成分,但它们当然无法调用derived class无权调用的成员函数。编译器两手一摊,无能为力。

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

为驳回编译器自动(暗自)提供的机能,可将对应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

为了防止这些函数被创建出来,你得自行声明他们,将这些函数声明成private。这样既可以阻止编译器暗自创建其专属版本,又可以成功阻止人们调用它。

对于member函数和friend函数可以设置一个base class并且继承它。这样编译器试着生成一个copy构造函数和一个copy assignment操作符,这些“编译器生成版”会尝试调用其base class的对应兄弟,那些调用会被编译器拒绝,因为其base class的拷贝构造函数是private。

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

polymorphic(带多态性质的)base classe应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。

Classes的设计目的不是作为base classes使用,或不是为了具备多态性,就不应该声明virtual析构函数。

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

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

如果程序遭遇一个“于析构期间发生的错误”后无法继续执行,要么“强迫结束程序”,要么“吞下因调用close而发生的异常”。

最佳方案是由用户自己调用一个close并不会对他们带来负担,而是给他们一个处理错误的机会,否则它们没机会响应。如果他们不认为这个机会有用(或许他们坚信不会有错误发生),可以忽略它。如果真有错误发生——如果close的确抛出异常——而且吞下该异常或结束程序,客户没有立场抱怨,毕竟让门曾经有机会第一手处理问题,而他们选择了放弃。可以类比当我们自己在windows操作系统上打开一个大软件,当程序卡死时,系统也会给我们一次立即结束该程序的机会,我们有权利选择继续执行该程序,也有权利立即结束该程序,但是一旦选择便要承担这个选择的后果,比如软件没有存档,或者电脑卡死。

条款09:绝不在构造和析构过程中调用virtual函数

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

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

令赋值操作符返回一个reference to *this。

将“=”重载后,返回*this可以实现链式赋值(连等的形式),若不是返回*this指针,那么在void情况下是不能返回值的,在其他返回值的条件下(比如返回this指针),将会把剩下需要赋值的变量附上其他值(比如地址),而不能将若干变量赋成同一个值了。

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

确保当对象自我赋值时operator= 有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

当重载后的“=”被用于自我赋值时(当然如果时给其他变量赋值它可能不会带来任何异常),可能会成员变量中指向的地址给删除,然后再将新值传递进来,若是两次值是同一个地址(自我赋值),那么准备传进去的地址已经被释放,将会令指针指向一块已经被释放的地区,这是不可取的。

解决办法就是加入一条“证同测试”的语句(if (this == &rhs) return *this;)如果是自我赋值则直接结束这个行为函数的执行。或者使用copy and swap技术,先将原先指向的地址赋给一个中间值,然后让新值传给原先的地址,最后删除中间值,实现这个行为函数。

此外,尽量不要使用值传递的方式给函数传参,因为这将会导致再复制一份副本出来。

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

Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并有两个copying函数共同调用。

任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private,所以你无法直接访问它们,你应该让derived class的copying函数调用相应的base class函数。

当你编写一个copying函数,请确保(1)复制所有local成员变量,(2)调用所有base classes内的适当的copying函数。

如果你发现你的copying构造函数和copying assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以消除copy构造函数和copy assignment操作符之间的代码重复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值