effect C++ 读书笔记

1. explicit的使用场景

防治隐士对象转换,比如 expilicit strng(int),这样strng=‘f’ 会报编译错误。

原则:尽量使用explicit,除非有更好的理由

2. 什么时候用copy构造函数,什么时候用赋值=构造函数

obj w=w1, 用copy,因为有新对象w构建,w=w1用赋值,因为没有新对象构建

3. 用const,enum,template inline代替#define

当需要常量时,用const,可以用const static来定义类作用域范围的常量,可以用enum来定义编译时需要知道的常量,比如数组大小提供需要编译时知道,如果用const变量会报错,可以用enum值代替,enum值还可以防止客户使用其引用和指针,如果想用宏定义函数,需要用template inline替换

4. 尽量用常量const

*号前,指向对象是常量,*号后,指针是常量

迭代器类似指针,如果迭代器前有const修饰,则说明迭代器本身是const,要得到一个指向对象是const的迭代器,需要用const_iterator

函数可以有两个版本,nonconst和const版本,nonconst版本可以直接调用const版本来避免代码重复

5. 初始化

成员函数的内置对象要显示为其初始化

使用初始化列表初始化成员函数,列表顺序最好跟变量声明顺序一致i

对于不同编译单元的static 变量的初始化,其顺序不确定,可以使用返回local static变量引用的函数代替non local static变量。

6. 编译器会自动生成构造,析构,copy构造,赋值构造函数

7. 若不想编译器自动生成,可以自己生成,若想阻止拷贝,可以将拷贝构造函数和赋值构造函数声明为private且不实现他们,也可以继承uncopiable基类

8. 虚析构函数

如果一个类里有至少一个虚函数,就应该定义虚析构函数,使得用户调用delete 指针的时候能调用子类的析构函数。如果函数不被设计为base类或者设计为base类但是不想用多态性质(如uncopiable类),则不需要定义虚析构函数。

9. 析构函数不抛出异常

不要在析构函数中抛出异常,因为这会导致不明确的后果,比如只析构了部分类型数据。可以直接退出程序或者隐藏错误。如果想要用户捕捉异常和决定处理方式,可以定义一个普通函数来释放资源,在普通函数里抛出异常,当用户没有调用普通函数时,在析构函数里面再调用释放资源函数。

10. 不要在构造函数和析构函数里调用虚函数

当构造函数调用base的构造函数,而base构造函数有虚函数时,调用的并不是子类的函数版本,因为这时子类还没有构建好。如果要实现构造函数里有子类特有的实现时,可以在base构造函数里让子类提供具体实现的信息。

11.  让赋值operate=返回reference this,这样可以写连续赋值计算

12. 在赋值操作符中, 判断参数和this是不是同一个对象,可以用copy and swap计数,先将参数的对象copy到temp对象,然后交换temp和this对象,这样可以杜绝构造时异常。

13. copy构造函数和赋值构造函数,请复制每一个成员变量,包括base的变量,请不要在copy构造函数里调用赋值构造函数,反之亦不要,如果要防止代码复制,可以写第三个函数然后让copy和赋值构造函数都调用它。

14. 将资源放到一个类中管理,比如将指针放到share_ptr中。

15. 资源管理器类的copy行为需精心设计,可以是禁止复制,引用计数,深度复制

16. 资源管理器类需要提供一个接口来返回底部的原始资源,通常显示转换比隐式转换更安全。

17. new和delete要成对使用,如果new用了【】, delete也要用【】

18 new出来的对象应立即存入资源管理对象,所以不要写类似这样的语句:func(std::share_ptr(new OBJ()), FUN1()), 需要先将share ptr语句单独拿出来,然后调用func, 因为有可能调用new后执行func1,最后调用share_ptr, 如果调用func1出错,new出来的东西将无法释放。

19. 更好地设计接口,让其更好的被使用而不会被误用

尽量不要用户自己管理资源,返回share ptr并带上合适的delete,设计类来包裹容易出错的内置类型数据,并在类上限制正确的范围,接口最好一致,且与内置类型一致

20. 传递函数参数时尽量用pass by reference const来代替pass by value,但是对于内置类型和STL 迭代器和函数对象,尽量pass by value

21. 在必须返回对象时不要返回reference和指针,不要让reference指向static和local static对象

22. 将成员变量设置为private,需要外部访问时用函数,protect和public都不具有封装性

23. 当设计一个类的扩展功能函数时,最好不要设计成类的成员函数,而是设计为工具类的成员函数,可以与之前的类在一个namespace里面。

24. 构建内置类型的时候可以考虑隐式类型转换,其他最好不考虑。当一个函数需要所有参数都支持隐式转换时,最好用non-member函数,如:operator*

25 swap

如果std里的swap不能满足你的性能要求,最好自己定义你的类的swap函数。

需要定义一个nonmember函数来调用menber函数的swap版本,而且如果你的class本身不是template的,可以特化std的swap函数,这样用户可以不用考虑地调用swap,就能找到正确的版本。

不要尝试添加任何新的成员和函数到std里面

不要在swap里面抛出异常

26. 转型操作

尽可能少做转型操作

const cast是唯一一个能将const限制去除的转换函数

dynamic cast是将一个base 类型转成derive 类型的转换

static cast是将一个derive类型转成base类型的, 编译时可检查

27. 不要返回类内部的hands,如reference,指针,迭代器,即使是const的也不好

28. 异常安全,当异常抛出时,保证类内部数据一致性,用资源管理类避免资源泄露,用swap保证函数调用后要么全部改变,要么回到初始状态。可以先创建一个tmp对象,在tmp对象上做所有改动,然后调用swap将所有改动都apply到目标对象上,这样保证要么全部改变,要么保持原样。

29. 编译依赖最小化

能用reference或指针的地方就不要用value

将声明和实现分开

依赖声明不要依赖class定义,可以有两种方法实现声明和定义分开,一种的pointer to implementation, 另一种是class接口

30. public 继承代表IS-A 关系,所有的base的行为在derive中都应该支持

31. 避免掩盖继承而来的名称

当derive定义一个和base相同的名称的时候,base的名称将不再有效,即使是参数不一样的函数名,如果想要base的名称不被遮盖,可以在derive中中using base:func;来使base的名称重现

32. 区分接口继承和实现继承

pure virtual是接口继承,pure virtual也可以有实现,但是derive需要强制有自己的实现,derived的实现可以调用base的实现。

非纯虚函数是接口和缺省实现的继承

non virtual是实现继承,意味所有derived都是这个行为。

33. virtual 函数外还有很多替代方案

用模板函数方式,定义一个non virtual函数,内部调用private virtual函数。在non virtual函数中可以添加一些公共的前缀和后缀代码。

用function pointer方式实现stratage模式,这样functon不是member函数,使得不同的对象可以有不同的策略,而且可以在运行时改变策略。但是这样non member function需要能够通过类的公共函数访问到必要的变量

由tr1::function构造stratage模式,tr1::function是任何兼容的function,可以是function object,可以是member function,可以是bind过后的function

经典的stratage模式,将function和做成一个继承体系,原来的类拥有一个function的base指针,有一个新的function实现,可以继承自function base类。

34. 不要重复定义base的带缺省值的函数

缺省值是静态绑定的,如果derive类改变了base的缺省值,当base指针指向derived类调用函数时,缺省值将是base的值,缺省值根据指针类型决定,所以如果有base类的函数有缺省值,不要在derive里重新实现这个函数,可以使用non virtual 函数调用virtual 不带缺省值的形式实现。

35. 组合的意义

组合有两种意义,在应用领域里,组合意味着has-a的关系,在软件领域里,组合可以是is implemented-in-terms-of的意义,例如使用底层的list实现set。

36. private 继承

private继承要谨慎使用,private继承只能使用base的public函数,且在derived类中都变成private函数,这意味着private继承的意义在于希望利用base类的实现过程,private继承不是is-a的关系,而是is implemented-in-terms-of的意义。

private的继承比普通的组合唯一的优点在于,当base 是个empty class时,组合需要耗费1个byte的空间,继承不需要。

37. 多重继承

多重继承容易造成名字歧义,钻石型继承共同的base类需要重复复制成员,如果使用virtual继承,则会因为virtual继承增加很多开销。唯一使用多重继承的理由是,public继承代表is-a,private继承实现。

38. template属于编译器多态, template属于隐式接口, class属于显示接口。

39. typename

template<class T> class A 和template<typename T> class A没有任何区别

当遇到嵌套从属类型时,必须用typename修饰

typename不能在base list和init list中定义

40. 基类是模板类型

当基类是模板类型的时候,derived类型不能直接调用基类里面的函数,因为基类在此时还没有指定具体类型,有可能某个基类特殊化版本不包含此函数了。如果要调用基类的函数,需要用this指定或者using声明。

41. 成员函数模板接受所有兼容类型

一个base指针可以接受derived对象指针,但是share_ptr<base> 却不能接受 share_ptr<derived>对象指针,因为这两者在编译器看来并没有继承关系,如果想要实现这个功能,需要写模板构造函数,即构造函数接受另一个模板参数,并使用原始指针来做转换。

赋值构造函数也有同样的需求。但是这两个模板成员函数并不能阻止编译器来帮实现默认构造函数,因为模板构造函数不是真正的构造函数。

42. 需要参数类型隐式转换时为模板定义非成员函数

当需要自动类型转换时,如operate* 操作在不同的类别上,需要定义模板的非成员函数,并且将这个非成员函数声明为类的friend函数。如果不定义friend函数,没有办法确定template类型,也没有办法调用类型隐式转换。

43. 使用traits class表现类型信息

当需要表现类型信息的时候,使用traits class,例如STL中的iterate category traits类表现iterate的类型,具体实现步骤如下:

在各个STL容器中有一个typeof iterate_category 定义iterate类型,在iterater_traits类中也定义一个category,调用模板类型的typeof

如果需要支持内置类型,但内置类型没有此typeof可以调用,需要特化此内置类型

当需要根据iterator_traits类型来做if操作时,可以定义一组重载函数,分别接受不同的iterator_traits category类型,再定义一个外围函数,调用重载函数,参数iterator_traits category,这样就能在编译期确定调用哪个重载函数。

44. template元编程

将运行期的动作移植到编译器,如上一条运行期的if使用函数重载功能移植到编译期。

boost库里有非常多的TMP

45. new-handler

可以设置当new操作失败的时候调用的回调,这个回调就是new-handler

一般回调里面可以做如下事情:让更多内存可使用,设置另一个回调,卸载new-handler(一旦没有注册new-handler,new操作失败就抛出异常),抛出异常,直接退出程序

可以在class里面定制自己的operator new操作符,此操作符里可以先定义class特定的new-handler,一旦new成功,可以将new-handler设置回原来的值

标准库中的new操作如果失败,会抛出异常,早期版本不抛出异常

46. new和delete的替换时机

当标准库中的new和delete不能满足你的应用需求的时候,可以替换标准的new和delete,一般有如下理由让你替换标准的new和delete。

加快速度,缩减开销,收集统计信息,强化对齐(标准中没有对齐),使内存成簇分配,一起使用的对象分配连续内存。

47. 编写new和delete所需要遵循的规则

new:处理size=0的情况,处理new-handler,循环调用new和new-handler直到成功或抛出异常,class专属版new需要检查size和类型是不是一致的。

delete:处理空指针,class专属版应该处理size和类型不一致情况

48. placement new和placement delete

标准的new接受一个void*,如果要与标准版的参数不一致,则构造出来的new叫placement new,placement delete同理

用placement new构建出来的对象,当malloc成功但构造失败,会寻找对应的placement delete(对应,即参数一致),但直接调用delete p用的是标准的delete

注意编写placement new和delete的时候,记得同时定义正常的new和delete,避免placement new覆盖(隐藏)了正常的new。

49. TR1标准库

TR1即technical report1

50。 boost






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值