读书笔记-Effective C++

来源:互联网 发布:增视能弱视训练软件 编辑:程序博客网 时间:2024/06/06 09:48

重新看了一遍自己写的,对于effective C++而言,没有实际的操作,都是耍流氓,文字是苍白的,只有实践过的东西,才能感受其条款的魅力。

条款一:

把C++视为一种联邦语言,因为支持,过程,面向对象,函数,泛型,元编程。

C语言的次语言,有C,Object-Oriented C++。这部分也就是C with Classes;Template C++(泛型编程,一般经验较少);STL

条款二:尽量用const,enum,inline替换define

要是出错,编译器只会定位define替换的东西,不方便找问题。用const就有类型等检测。

另外define不考虑scope(作用域),因此不能提供任何的封装性

enum{ xxx = 5};

这个例子很nice。

引出了泛型:



但是预处理器还是有用的,include,ifdef都有他自己的光和热。

条款3:尽可能使用const

例如,函数的输入变量,甚至是输出变量,假如不更改的话,请用const

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

条款4:确定对象被使用前已经被初始化

确保每一个构造函数都将对象的每一个成员初始化。

构造函数里写的,xx=xxx,都是赋值,而不是初始化

成员列表是初始化,且通常效率更高

base classes 更早于其derived classes被初始化,class的初始化顺序,以声明的次序为准

请记住:为内置型对象进行手工初始化,因为C++不保证初始化他们;

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

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

默认构造函数,默认copy构造函数,默认析构函数(一般是non-virtual)

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

编译器产生的都是public,你可以写一个copy构造函数,而且是private,防止别人调用他(声明为private,故意不实现他们)

为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现

条款7:为多态积累声明virtual析构函数

这个还是能理解,我的理解,父类指针指向子类,然后析构,假如析构函数不是virtual,则会只释放父类,子类不释放。

嗯,理解是对,外加一句,当一个类有virtual,他往往被作为父类,假如他没有virtual,你把析构变成virtual,体积会变大

假如有个类,他析构不是virtual,请不要继承(java有类似final class来禁止继承)

一个新现象假如类A只有一些基本变量例如(int a,int b ),然后一个类B继承了A,有自己的int c;int d。有如下:

A *a = new B();delete a;
请问这个有内存泄漏么,感觉是有的,因为没法调用B的析构函数,但是实际在linux测试中,用valgrind软件查看内存泄漏问题,发现是没有内存泄漏(猜测原因,基本变量本身不需要析构去处理,系统也许会自动将这些析构掉,虽然a不知道B的大小,但是在内存中,申请的区域是可以有大小指示的,猜测利用这个,析构了基本变量)

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

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能跑抛出异常,析构得捕捉,吞下这个错误

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

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

因为这类调用从不下降至derived class

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

好习惯

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

他指的是,比如出现a=a这种语句,因为一般的=,会delete原来的值,然后再new新的值,假如是=自己,delete把自己删掉了,再new没有意义,一种做法,是=之前,检测俩个的地址是不是同一个;另一个办法是,不delete,复制一个备份,然后原来的和这个备份作swap。

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

如果你为class添加一个成员变量,你必须同时修改copying函数(以及所有构造函数)

不要尝试以某个copying函数实现另一个copying函数,例如,子类实现copying,不要想着去实现父类的,而应该调用base class的函数(有些private)

条款13:以对象管理资源

不要指望delete会释放资源(有可能之前有异常,或者提前return),得把资源放在类里,利用析构函数,自动释放资源。

这里涉及到了智能指针(auto_ptr),是“资源取得时间便是初始化时机”(RAII),就是指针会管理对象,假如不再调用了,则会自动运行析构。且auto_ptr在运行copy等函数时,复制所得的指针会取得资源,原来的会变成null(假如多个auto_ptr指向资源,会被重复释放)但是这个auto_ptr还有替代方案,“引用技术型智慧指针”RCSP,也是智能指针,能追踪,有多少对象指向这个资源,并在无人指向的时候释放(有点类似jvm的垃圾回收机制了吧),RCSP无法打破环状引用。

为防止资源泄漏,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源。

两个常用的RAII classes,是tr1:shared_ptr和auto_ptr。

条款14:在资源管理类中小心copying行为 ???

这个就不太好理解了。就稍微写几句

请复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。

普遍而常见的RAII class copying行为是:抑制copying、实行引用计数法。

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

tr1:shared_ptr和auto_ptr都提供一个get函数,用来执行显式转换,也就是会返回只能指针内部的原始指针。但是当请求大的时候,就不停的get,显得麻烦,但是显式比较安全。

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

意思就是假如new了一个数字,那你delete也得delete [] xxx

有些什么用了typedef,要是他里面有[],其实外界并看不出来,因此有个规则就是typedef时,不要包含数组

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

C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。

processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());

有的编译器,先priority,然后new,在用智能指针的构造函数,有的却是,new,然后pri,假如pri有问题,则new了以后没人管了,会有内存泄漏,这里就要采取分离的方式,就是std::tr1::shared_ptr<Widget>(new Widget)先完成,然后再调用processWidget(xxx,priority());这样就行。其实就是细节考虑

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

“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。

“防止甩”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范dll问题,可被用来自动解除互斥锁。

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

新type的对象应该如何被创建和销毁;

对象的初始化和对象的赋值该有怎么样的差别;

新type的对象如果被passed by value,意味着什么

什么是新type的合法值。

你的新type需要配合某个继承图系吗

你的新type需要什么样的转换?

什么样的操作符和函数对此新type而言是合理的。。。

条款20:宁可以pass-by-reference-to-const 替代pass-by-value

值传递,要是是类的话,还设计到累的构造和销毁,代价很大。

利用常引用替代,高效得多,没有任何对象被创建(const是必须的,防止被改变)

另外要是传入类的派生类,会被切割。

假如是内置类型(int),pass by value 往往比pass by reference效率高些、(stl也是)

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

他这个主要是,返回的东西是需要函数临时构造的,是个local,你返回一个reference,那就会非常糟糕。

必须返回,就的在heap里构造,然后再返回(还是会扯到构造函数),也不要这么做,因为,不知道怎么delete

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

这样,只能通过函数访问,而且可以控制有些变量只能写,有些只能读。

另外,以封装的角度,别人只是get变量,你可以去做更改

protected并不比public更具备封装性,public影响所有class,protected影响所有derived class

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

封装的目的,用户没法访问大部分东西,开发者也就可以去维护,改动后,不影响用户使用。

然后这个条款的目的也是增加封装性,其实举个例子就行。

比如class有个close函数,每次都会调用class,又一次,这个close要改成带参数的,几乎所有的地方都要改,假如用个函数封装一下,然后其他人调用这个函数,close加参数,只需要去改变这个函数就行(这个函数不能加参数)。我是这么理解的

条款24:若所有参数皆需要类型转换,请为此采用non-member函数???

条款25:考虑协议写一个不抛出异常的swap函数。???

当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

如果你提供一个member swap,也改提供一个non-member swap调用前者,

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

意思是,不要每次一上来就各种定义,这样,要是中间有一步有问题,return了,那些定义的变量都没有用到,开销会很大。

可以增加程序的清晰度并改善程序效率

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

C++4种转型:cost_cast:常量性转除。

dynamic_cast:安全向下转型,耗费也许很大。

reinterpret_cast:执行低级转型

static_cast:强迫隐转换

如果可以,在注重效率的代码中避免dynamic_casts

假如一定要转型,记得放在某个函数背后,不要让客户调用

宁可使用C++新式的转型方式。

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

条款29:为异常安全而努力是指的的。

当异常被抛出时,带有异常安全性的函数会,不泄露任何资源;不允许数据破坏

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

inline过多,造成程序太大(因为,每次都是用函数本体替代),是编译器期间的行为

有时候虽然你写了inline,编译器不一定接受

不要因为function templates出现在头文件,就申明为inline(然后我并不清楚申明是templates)

条款31:将文件间的编译依存关系降至最低(最好还是看例子)

情境:你改了一个class中的private,然后make,结果,整个世界都重新编译和连接了。

原因是:C++并没有把“将接口从实现中分离”这事做得很好。

例如Person类里面有个Address类作为private,那么编译的时候,必须带上

编译器必须在编译期间知道对象的大小

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

is-a(英语:subsumption,包含架构)指的是类的父子继承关系,例如类D是另一个类B的子类(类B是类D的父类)

public inheritance 意味着 is-a的关系

意味着,base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。(比如bird(会飞),企鹅是鸟,public继承,但是他不会飞,因此这个就不行)

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

意思就是base 有 f1 f2 f3函数,不管是什么方式(是否虚),derived class 里有f2 f3,则 derived的类,不能访问f1,只能访问f2,f3且都是继承类的,除非使用using,把base的作用域引用进来。以及,指针貌似也有办法调用f1.

条款34:区分接口继承和实现继承

成员函数的接口总是会被继承

声明一个pure virtual函数的目的是为了让derived classes只继承接口

声明简朴的(非纯)impure virtual 函数的目的,是让derived classes继承该函数的接口和缺省实现。

non-virtual函数具体制定接口继承以及强制性实现继承

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

NVI手法,令客户通过public non-virtual 成员函数调用private virtual函数

替代方案还包括Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式

将技能从成员函数转移到class外部函数的一个缺点是,非成员函数无法访问class的non-public

tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物

条款36:绝不重新定义继承而来的non-virtual函数

这个书上给的例子是常见的,因为不同类,同一个函数的偏移量不一样(假如是non-virtual)

因此,重新定义没有意义,假如重新就应该是virtual

条款37:绝不重新定义继承而来的缺省参数值

因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定的。

条款38:通过符合塑模出has-a或“根据某物实现出” 不懂

条款39:明智而审慎地使用private继承

private继承不是因为两个对象存在任何观念上的关系,而是为了采取base class中已经备妥的某些特性,private继承,纯粹是一种实现技术。

条款40:明智而审慎地使用多重继承

比单一肯定复杂,而且容易导致新的歧义性。

多重继承有正当用途。其中一个情节涉及“public继承某个Interface class”,“private继承某个协助实现的class”的两相组合。

条款41-条款48是泛型,基本还不懂,就先不看规则了

条款49:了解new-handler的行为 不是很懂

std::set_new_handler(函数名字),new不行的时候会执行这个。

new-handler的目的是:让更多内存可被使用;安装另一个new-handler(意思就是我不行了,找别人来解决这个问题);卸除new-handler(null);抛出bad_alloc的异常;不返回。


暂时先写这么多,感觉得上实际工程去理解,记得多看几遍。有些不太好理解,需要实际支撑一下。

最后作者提了两本书《effective STL》 《more effective C++》相当于是进阶吧


读这本书有感,这本书还是需要实际做一些支撑,虽然看了一遍,但是可能代码碰到的比较少,因此感触不是那么深。也说说自己体会吧。例如比较简单的条款:对于non-virtual成员函数,继承的时候,千万不要修改(或者说同名)。这个的感触还行,比如A有函数a,B有函数a,假如例化一个B,不同的指针调用的a函数是不同的,但是我们的对象是同一个,调用的咋就不一样了(虽然,某种程度上有一定的实际意义)但是还是不要用,会和虚函数发生混淆。其次一个条款,绝对不在构造函数和析构函数里调用virtual,比如父类的构造函数调用了虚函数,子类,调用父类构造的时候(期待,构造函数的虚函数会调用自己的虚函数,然后并不会,因为处于base阶段是不会下降到derived阶层的,简单来说,virtual此时就等于普通函数)。

原创粉丝点击