重新看了一遍自己写的,对于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
有的编译器,先priority,然后new,在用智能指针的构造函数,有的却是,new,然后pri,假如pri有问题,则new了以后没人管了,会有内存泄漏,这里就要采取分离的方式,就是std::tr1::shared_ptr
条款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此时就等于普通函数)。